feat: add markdown module header and async bash execution result structs

Introduce `include/markdown.h` with retoor copyright header and add `r_process_result_t` struct plus `r_bash_execute_ext` / `r_process_result_free` declarations to `include/bash_executor.h` for advanced async process handling.
This commit is contained in:
2026-01-29 05:54:10 +00:00
parent 8215d7012d
commit dccd79c05a
98 changed files with 6682 additions and 817 deletions
+122 -150
View File
@@ -5,6 +5,7 @@
#include "r_config.h"
#include "tool.h"
#include "context_manager.h"
#include "markdown.h"
#include <json-c/json.h>
#include <stdio.h>
#include <stdlib.h>
@@ -65,139 +66,6 @@ static const char *passive_phrases[] = {
extern tool_registry_t *tools_get_registry(void);
agent_handle agent_create(const char *goal, messages_handle messages) {
struct agent_t *agent = calloc(1, sizeof(struct agent_t));
if (!agent) return NULL;
if (goal) {
agent->goal = strdup(goal);
if (!agent->goal) {
free(agent);
return NULL;
}
}
r_config_handle cfg = r_config_get_instance();
agent->iteration_count = 0;
agent->max_iterations = AGENT_MAX_ITERATIONS;
agent->tool_retry_count = 0;
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
agent->state = AGENT_STATE_IDLE;
agent->start_time = time(NULL);
agent->verbose = r_config_is_verbose(cfg);
if (messages) {
agent->messages = messages;
agent->owns_messages = false;
} else {
agent->messages = messages_create(r_config_get_session_id(cfg));
agent->owns_messages = true;
}
if (!agent->messages) {
free(agent->goal);
free(agent);
return NULL;
}
const char *system_msg = r_config_get_system_message(cfg);
if (!system_msg || !*system_msg) {
bool has_system = false;
for (int i = 0; i < messages_count(agent->messages); i++) {
struct json_object *msg = messages_get_object(agent->messages, i);
struct json_object *role;
if (json_object_object_get_ex(msg, "role", &role)) {
const char *role_str = json_object_get_string(role);
if (role_str && strcmp(role_str, "system") == 0) {
has_system = true;
break;
}
}
}
if (!has_system) {
messages_add(agent->messages, "system",
"You are an autonomous AI agent with full system access through function calling. "
"You have the spawn_agent tool to create specialized sub-agents for different tasks. "
"Use spawn_agent extensively for: "
"- research tasks (researcher persona) "
"- development tasks (developer persona) "
"- security audits (security persona) "
"When web_search returns results with URLs, spawn researcher agents to fetch and analyze the content. "
"Always break complex tasks into sub-tasks using agents for better orchestration. "
"Continue iterating until goals are fully achieved, using appropriate tools and agents.");
}
} else if (system_msg && *system_msg) {
bool has_system = false;
for (int i = 0; i < messages_count(agent->messages); i++) {
struct json_object *msg = messages_get_object(agent->messages, i);
struct json_object *role;
if (json_object_object_get_ex(msg, "role", &role)) {
const char *role_str = json_object_get_string(role);
if (role_str && strcmp(role_str, "system") == 0) {
has_system = true;
break;
}
}
}
if (!has_system) {
messages_add(agent->messages, "system", system_msg);
}
}
agent->http = http_client_create(r_config_get_api_key(cfg));
if (!agent->http) {
if (agent->owns_messages) {
messages_destroy(agent->messages);
}
free(agent->goal);
free(agent);
return NULL;
}
agent->tools = tools_get_registry();
return agent;
}
void agent_destroy(agent_handle agent) {
if (!agent) return;
if (agent->http) http_client_destroy(agent->http);
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
free(agent->goal);
free(agent->last_error);
free(agent);
}
void agent_set_max_iterations(agent_handle agent, int max) {
if (agent) agent->max_iterations = max;
}
void agent_set_verbose(agent_handle agent, bool verbose) {
if (agent) agent->verbose = verbose;
}
void agent_set_is_subagent(agent_handle agent, bool is_subagent) {
if (agent) agent->is_subagent = is_subagent;
}
void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry) {
if (agent && registry) agent->tools = registry;
}
agent_state_t agent_get_state(agent_handle agent) {
return agent ? agent->state : AGENT_STATE_ERROR;
}
const char *agent_get_error(agent_handle agent) {
return agent ? agent->last_error : NULL;
}
int agent_get_iteration_count(agent_handle agent) {
return agent ? agent->iteration_count : 0;
}
static void agent_set_error(agent_handle agent, const char *error) {
if (!agent) return;
free(agent->last_error);
@@ -215,10 +83,11 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
if (role && message) {
messages_add(agent->messages, role, message);
if (r_config_use_tools(cfg) && agent->tools) {
json_object_object_add(root, "tools",
tool_registry_get_descriptions(agent->tools));
}
}
if (r_config_use_tools(cfg) && agent->tools) {
json_object_object_add(root, "tools",
tool_registry_get_descriptions(agent->tools));
}
json_object_object_add(root, "messages",
@@ -331,7 +200,6 @@ static bool agent_response_indicates_incomplete(const char *content) {
}
// Check for passive/closing phrases (Overrides incomplete indicators)
// Example: "I will be here if you need me." -> Contains "I will" but is passive.
for (int i = 0; passive_phrases[i]; i++) {
if (strcasestr(content, passive_phrases[i])) return false;
}
@@ -353,6 +221,113 @@ static bool agent_response_indicates_incomplete(const char *content) {
return false;
}
agent_handle agent_create(const char *goal, messages_handle messages) {
struct agent_t *agent = calloc(1, sizeof(struct agent_t));
if (!agent) return NULL;
if (goal) {
agent->goal = strdup(goal);
if (!agent->goal) {
free(agent);
return NULL;
}
}
r_config_handle cfg = r_config_get_instance();
agent->iteration_count = 0;
agent->max_iterations = AGENT_MAX_ITERATIONS;
agent->tool_retry_count = 0;
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
agent->state = AGENT_STATE_IDLE;
agent->start_time = time(NULL);
agent->verbose = r_config_is_verbose(cfg);
if (messages) {
agent->messages = messages;
agent->owns_messages = false;
} else {
agent->messages = messages_create(r_config_get_session_id(cfg));
agent->owns_messages = true;
}
if (!agent->messages) {
free(agent->goal);
free(agent);
return NULL;
}
const char *system_msg = r_config_get_system_message(cfg);
if (system_msg && *system_msg) {
bool has_system = false;
for (int i = 0; i < messages_count(agent->messages); i++) {
struct json_object *msg = messages_get_object(agent->messages, i);
struct json_object *role;
if (json_object_object_get_ex(msg, "role", &role)) {
const char *role_str = json_object_get_string(role);
if (role_str && strcmp(role_str, "system") == 0) {
has_system = true;
break;
}
}
}
if (!has_system) {
messages_add(agent->messages, "system", system_msg);
}
}
agent->http = http_client_create(r_config_get_api_key(cfg));
if (!agent->http) {
if (agent->owns_messages) {
messages_destroy(agent->messages);
}
free(agent->goal);
free(agent);
return NULL;
}
agent->tools = tools_get_registry();
return agent;
}
void agent_destroy(agent_handle agent) {
if (!agent) return;
if (agent->http) http_client_destroy(agent->http);
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
free(agent->goal);
free(agent->last_error);
free(agent);
}
void agent_set_max_iterations(agent_handle agent, int max) {
if (agent) agent->max_iterations = max;
}
void agent_set_verbose(agent_handle agent, bool verbose) {
if (agent) agent->verbose = verbose;
}
void agent_set_is_subagent(agent_handle agent, bool is_subagent) {
if (agent) agent->is_subagent = is_subagent;
}
void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry) {
if (agent && registry) agent->tools = registry;
}
agent_state_t agent_get_state(agent_handle agent) {
return agent ? agent->state : AGENT_STATE_ERROR;
}
const char *agent_get_error(agent_handle agent) {
return agent ? agent->last_error : NULL;
}
int agent_get_iteration_count(agent_handle agent) {
return agent ? agent->iteration_count : 0;
}
char *agent_run(agent_handle agent, const char *user_message) {
if (!agent) return NULL;
@@ -384,14 +359,14 @@ char *agent_run(agent_handle agent, const char *user_message) {
if (agent->iteration_count > agent->max_iterations) {
agent->state = AGENT_STATE_MAX_ITERATIONS;
agent_set_error(agent, "Maximum iterations reached");
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] Max iterations (%d) reached\n", agent->max_iterations);
}
free(json_data);
break;
}
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] Iteration %d/%d\n",
agent->iteration_count, agent->max_iterations);
}
@@ -404,7 +379,6 @@ char *agent_run(agent_handle agent, const char *user_message) {
free(json_data);
json_data = agent_build_request(agent, NULL, NULL);
agent->state = AGENT_STATE_RUNNING;
// Don't increment iteration_count for retries due to context
agent->iteration_count--;
continue;
} else {
@@ -424,7 +398,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
agent_set_error(agent, "API request failed after retries");
break;
}
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] API error, retry %d/%d\n",
agent->tool_retry_count, agent->max_tool_retries);
}
@@ -442,9 +416,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
char *content = agent_get_content(choice);
if (content && *content) {
// Print content immediately to the user (only if NOT a sub-agent)
if (!agent->is_subagent) {
extern void parse_markdown_to_ansi(const char *content);
parse_markdown_to_ansi(content);
printf("\n");
}
@@ -464,7 +436,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
bool has_tools = agent_has_tool_calls(choice);
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] has_tool_calls=%s\n", has_tools ? "true" : "false");
}
@@ -473,7 +445,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
struct json_object *tool_calls = agent_get_tool_calls(choice);
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
int num_tools = json_object_array_length(tool_calls);
fprintf(stderr, "[Agent] Executing %d tool(s)\n", num_tools);
}
@@ -496,7 +468,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
}
} else if (content && agent_response_indicates_incomplete(content)) {
if (agent->verbose) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
}
@@ -530,7 +502,7 @@ char *agent_chat(const char *user_message, messages_handle messages) {
char *response = agent_run(agent, user_message);
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
}
agent_destroy(agent);
@@ -545,9 +517,9 @@ char *agent_chat_with_limit(const char *user_message, int max_iterations, messag
char *response = agent_run(agent, user_message);
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
}
agent_destroy(agent);
return response;
}
}
+131 -154
View File
@@ -17,182 +17,159 @@
#define DEFAULT_TIMEOUT 300
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
if (!command) {
return strdup("Error: null command");
}
void r_process_result_free(r_process_result_t *res) {
if (!res) return;
free(res->output);
free(res->log_path);
free(res);
}
if (timeout_seconds <= 0) {
timeout_seconds = DEFAULT_TIMEOUT;
}
static char *get_log_path(int pid) {
char *path = NULL;
if (asprintf(&path, "/tmp/r_process_%d.log", pid) == -1) return NULL;
return path;
}
size_t len = strlen(command);
char *cmd_with_nl = malloc(len + 2);
if (!cmd_with_nl) {
return strdup("Error: memory allocation failed");
}
r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds, bool async) {
if (!command) return NULL;
strcpy(cmd_with_nl, command);
if (len > 0 && cmd_with_nl[len - 1] != '\n') {
cmd_with_nl[len] = '\n';
cmd_with_nl[len + 1] = '\0';
} else if (len == 0) {
cmd_with_nl[0] = '\n';
cmd_with_nl[1] = '\0';
}
r_process_result_t *res = calloc(1, sizeof(r_process_result_t));
if (!res) return NULL;
if (timeout_seconds <= 0) timeout_seconds = DEFAULT_TIMEOUT;
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
int script_fd = mkstemps(tmp_script, 3);
if (script_fd == -1) {
free(cmd_with_nl);
return strdup("Error: failed to create temp script");
res->output = strdup("Error: failed to create temp script");
return res;
}
if (write(script_fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) {
close(script_fd);
free(cmd_with_nl);
unlink(tmp_script);
return strdup("Error: failed to write to temp script");
}
dprintf(script_fd, "%s\n", command);
close(script_fd);
free(cmd_with_nl);
char *output = NULL;
size_t total_size = 0;
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
unlink(tmp_script);
res->output = strdup("Error: pipe failed");
return res;
}
if (interactive) {
// For interactive mode, we still use system() but it doesn't easily support capturing output while timing out
// Given the requirement, we'll try to use a simple timeout for interactive too if we can,
// but typically interactive means user is at it.
// However, user said "prevent hanging processes".
pid_t pid = fork();
if (pid == -1) {
close(pipe_fds[0]);
close(pipe_fds[1]);
unlink(tmp_script);
res->output = strdup("Error: fork failed");
return res;
}
if (pid == 0) {
// Child
setsid(); // New session to prevent signals to parent
close(pipe_fds[0]);
char *run_cmd = NULL;
if (asprintf(&run_cmd, "timeout %ds bash %s", timeout_seconds, tmp_script) == -1) {
unlink(tmp_script);
return strdup("Error: asprintf failed");
}
int status = system(run_cmd);
free(run_cmd);
// Setup log file for child
char *log_p = get_log_path(getpid());
int log_fd = open(log_p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
free(log_p);
if (WIFEXITED(status) && WEXITSTATUS(status) == 124) {
output = strdup("Error: Command timed out in interactive mode.");
if (log_fd != -1) {
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
close(log_fd);
} else {
if (asprintf(&output, "Command exited with status %d", status) == -1) {
output = strdup("Command completed.");
}
}
} else {
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
unlink(tmp_script);
return strdup("Error: pipe failed");
}
pid_t pid = fork();
if (pid == -1) {
close(pipe_fds[0]);
close(pipe_fds[1]);
unlink(tmp_script);
return strdup("Error: fork failed");
}
if (pid == 0) {
// Child
close(pipe_fds[0]);
dup2(pipe_fds[1], STDOUT_FILENO);
dup2(pipe_fds[1], STDERR_FILENO);
close(pipe_fds[1]);
char *args[] = {"bash", tmp_script, NULL};
execvp("bash", args);
exit(1);
}
// Parent
close(pipe_fds[1]);
int out_fd = pipe_fds[0];
struct poll_pfd {
int fd;
short events;
short revents;
} pfd;
pfd.fd = out_fd;
pfd.events = POLLIN;
// Also pipe back to parent if possible (redundant but safe for short commands)
// Actually, let's just use log file for everything.
close(pipe_fds[1]);
time_t start_time = time(NULL);
bool timed_out = false;
while (true) {
time_t now = time(NULL);
int remaining = timeout_seconds - (int)(now - start_time);
if (remaining <= 0) {
timed_out = true;
break;
}
int ret = poll((struct pollfd *)&pfd, 1, remaining * 1000);
if (ret == -1) {
if (errno == EINTR) continue;
break;
}
if (ret == 0) {
timed_out = true;
break;
}
if (pfd.revents & POLLIN) {
char buffer[4096];
ssize_t bytes = read(out_fd, buffer, sizeof(buffer) - 1);
if (bytes <= 0) break;
buffer[bytes] = '\0';
// Print to stderr for user
fprintf(stderr, "\033[2m%s\033[0m", buffer);
fflush(stderr);
char *new_output = realloc(output, total_size + (size_t)bytes + 1);
if (!new_output) {
break;
}
output = new_output;
memcpy(output + total_size, buffer, (size_t)bytes);
total_size += (size_t)bytes;
output[total_size] = '\0';
} else if (pfd.revents & (POLLHUP | POLLERR)) {
break;
}
}
if (timed_out) {
kill(-pid, SIGKILL); // Kill process group if possible
kill(pid, SIGKILL);
const char *timeout_msg = "\n[Error: Command timed out after %d seconds]\n";
char *msg = NULL;
if (asprintf(&msg, timeout_msg, timeout_seconds) != -1) {
size_t msg_len = strlen(msg);
char *new_output = realloc(output, total_size + msg_len + 1);
if (new_output) {
output = new_output;
strcpy(output + total_size, msg);
total_size += msg_len;
}
free(msg);
}
fprintf(stderr, "\033[1;31m%s\033[0m", "\n[Timeout reached, process terminated]\n");
}
close(out_fd);
waitpid(pid, NULL, WNOHANG);
char *args[] = {"bash", tmp_script, NULL};
execvp("bash", args);
exit(1);
}
if (!output) {
output = strdup("");
// Parent
res->pid = pid;
res->log_path = get_log_path(pid);
res->is_running = true;
close(pipe_fds[1]);
close(pipe_fds[0]);
if (async) {
res->output = strdup("Process started in background.");
usleep(100000); // Give child time to start
unlink(tmp_script);
return res;
}
// Wait for timeout
time_t start_time = time(NULL);
long last_read_pos = 0;
while (true) {
int status;
pid_t ret = waitpid(pid, &status, WNOHANG);
// Read new content from log file and print to stdout for the user
FILE *f_tail = fopen(res->log_path, "r");
if (f_tail) {
fseek(f_tail, last_read_pos, SEEK_SET);
char tail_buf[4096];
while (fgets(tail_buf, sizeof(tail_buf), f_tail)) {
fprintf(stdout, "[%d]\t %s", pid, tail_buf);
fflush(stdout);
}
last_read_pos = ftell(f_tail);
fclose(f_tail);
}
if (ret == pid) {
res->is_running = false;
res->exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
break;
} else if (ret == -1) {
res->is_running = false;
break;
}
if (time(NULL) - start_time >= timeout_seconds) {
res->timed_out = true;
break;
}
usleep(50000); // 100ms -> 50ms for better responsiveness
}
// Read log file for output
FILE *log_f = fopen(res->log_path, "r");
if (log_f) {
fseek(log_f, 0, SEEK_END);
long size = ftell(log_f);
rewind(log_f);
if (size >= 0) {
res->output = malloc((size_t)size + 1);
if (res->output) {
size_t rs = fread(res->output, 1, (size_t)size, log_f);
res->output[rs] = '\0';
}
}
fclose(log_f);
}
if (!res->output) res->output = strdup("");
unlink(tmp_script);
return output;
}
return res;
}
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
// Legacy support wrapper
r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false);
char *out = strdup(res->output);
r_process_result_free(res);
return out;
}
+193
View File
@@ -0,0 +1,193 @@
// retoor <retoor@molodetz.nl>
#include "markdown.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.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.
*/
static int is_keyword(const char *word) {
const char *keywords[] = {
"int", "float", "double", "char", "void", "if", "else", "while", "for",
"return", "struct", "printf", "let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub",
"const", "static", "def", "class", "import", "from", "as", "with", "try", "except",
"finally", "lambda", "async", "await", "public", "private", "protected", "interface", "extends",
"implements", "new", "synchronized", "var", "switch", "case", "break", "continue",
"namespace", "template", "typename", "virtual", "override", "friend", "package", "func", "type", "go", "defer", "select",
"then", "elif", "fi", "esac", "do", "done", "using"};
for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
if (strcmp(word, keywords[i]) == 0) return 1;
}
return 0;
}
void highlight_code(const char *code) {
const char *ptr = code;
char buffer[4096];
size_t index = 0;
while (*ptr) {
if (isalpha((unsigned char)*ptr) || *ptr == '_') {
while (isalnum((unsigned char)*ptr) || *ptr == '_') {
if (index < sizeof(buffer) - 1) buffer[index++] = *ptr++;
else ptr++;
}
buffer[index] = '\0';
if (is_keyword(buffer)) printf(FG_BLUE "%s" RESET FG_YELLOW, buffer);
else printf("%s", buffer);
index = 0;
} else if (isdigit((unsigned char)*ptr)) {
while (isdigit((unsigned char)*ptr)) {
if (index < sizeof(buffer) - 1) buffer[index++] = *ptr++;
else ptr++;
}
buffer[index] = '\0';
printf(FG_CYAN "%s" RESET FG_YELLOW, buffer);
index = 0;
} else {
putchar(*ptr);
ptr++;
}
}
}
void parse_markdown_to_ansi(const char *markdown) {
const char *ptr = markdown;
bool is_start_of_line = true;
while (*ptr) {
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 = strndup(code_start, (size_t)(code_end - code_start));
printf(FG_YELLOW);
highlight_code(block_buffer);
printf(RESET);
free(block_buffer);
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;
}
}
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, (size_t)(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;
}
}
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, (size_t)(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, (size_t)(end - ptr), stdout); ptr = end + 2; }
else { fputs(ptr, stdout); ptr += strlen(ptr); }
printf(RESET); continue;
}
if (strncmp(ptr, "~~", 2) == 0) {
printf(STRIKETHROUGH); ptr += 2;
const char *end = strstr(ptr, "~~");
if (end) { fwrite(ptr, 1, (size_t)(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, (size_t)(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, (size_t)(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, (size_t)(text_end - text_start), stdout);
printf(RESET " ("); printf(ITALIC FG_CYAN);
fwrite(url_start, 1, (size_t)(url_end - url_start), stdout);
printf(RESET ")"); ptr = url_end + 1; continue;
}
}
}
if (*ptr == '\n') is_start_of_line = true;
else if (!isspace((unsigned char)*ptr)) is_start_of_line = false;
putchar(*ptr);
ptr++;
}
}
-351
View File
@@ -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++;
}
}
+225 -57
View File
@@ -3,106 +3,274 @@
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include "markdown.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
static struct json_object *python_execute_get_description(void);
static char *python_execute_execute(tool_t *self, struct json_object *args);
static void python_execute_print_action(const char *name, struct json_object *args);
static struct json_object *python_status_get_description(void);
static char *python_status_execute(tool_t *self, struct json_object *args);
static struct json_object *python_terminate_get_description(void);
static char *python_terminate_execute(tool_t *self, struct json_object *args);
static const tool_vtable_t python_execute_vtable = {
.get_description = python_execute_get_description,
.execute = python_execute_execute,
.print_action = python_execute_print_action
};
static tool_t python_execute_tool = {
.vtable = &python_execute_vtable,
.name = "python_execute"
static const tool_vtable_t python_status_vtable = {
.get_description = python_status_get_description,
.execute = python_status_execute,
.print_action = NULL
};
tool_t *tool_python_execute_create(void) {
return &python_execute_tool;
}
static const tool_vtable_t python_terminate_vtable = {
.get_description = python_terminate_get_description,
.execute = python_terminate_execute,
.print_action = NULL
};
static tool_t python_execute_tool = { .vtable = &python_execute_vtable, .name = "python_execute" };
static tool_t python_status_tool = { .vtable = &python_status_vtable, .name = "python_get_status" };
static tool_t python_terminate_tool = { .vtable = &python_terminate_vtable, .name = "python_terminate" };
tool_t *tool_python_execute_create(void) { return &python_execute_tool; }
tool_t *tool_python_get_status_create(void) { return &python_status_tool; }
tool_t *tool_python_terminate_create(void) { return &python_terminate_tool; }
static void python_execute_print_action(const char *name, struct json_object *args) {
(void)name;
(void)args;
fprintf(stderr, " \033[1m-> Executing Python code\033[0m\n");
struct json_object *source;
if (json_object_object_get_ex(args, "source", &source)) {
const char *src = json_object_get_string(source);
fprintf(stderr, " \033[1;34m┌─── Python Source Code ─────────────────────────────────────\033[0m\n");
char *copy = strdup(src);
char *line;
char *saveptr;
int line_num = 1;
line = strtok_r(copy, "\n", &saveptr);
while (line) {
fprintf(stderr, " \033[1;34m│\033[0m \033[2m%3d |\033[0m ", line_num++);
highlight_code(line);
fprintf(stderr, "\033[0m\n");
line = strtok_r(NULL, "\n", &saveptr);
}
fprintf(stderr, " \033[1;34m└────────────────────────────────────────────────────────────\033[0m\n");
free(copy);
}
}
static char *python_execute_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *source_obj, *timeout_obj, *async_obj;
if (!json_object_object_get_ex(args, "source", &source_obj)) return strdup("Error: missing 'source'");
struct json_object *source_obj;
if (!json_object_object_get_ex(args, "source", &source_obj)) {
return strdup("Error: missing 'source' argument");
}
int timeout = 30;
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
bool async = false;
if (json_object_object_get_ex(args, "async", &async_obj)) async = json_object_get_boolean(async_obj);
const char *source_code = json_object_get_string(source_obj);
char *output = NULL;
int fd = -1;
char tmp_file[] = "/tmp/r_python_XXXXXX.py";
int fd = mkstemps(tmp_file, 3);
if (fd == -1) return strdup("Error: failed to create temp python file");
dprintf(fd, "%s\n", source_code);
close(fd);
char tmp_file[] = "/tmp/r_python_tool_XXXXXX.py";
fd = mkstemps(tmp_file, 3);
if (fd == -1) {
return strdup("Failed to create temporary file for Python code.");
char cmd[4096];
snprintf(cmd, sizeof(cmd), "python3 '%s' && rm '%s' || { rm '%s'; exit 1; }", tmp_file, tmp_file, tmp_file);
r_process_result_t *res = r_bash_execute_ext(cmd, timeout, async);
struct json_object *root = json_object_new_object();
json_object_object_add(root, "pid", json_object_new_int(res->pid));
json_object_object_add(root, "output", json_object_new_string(res->output));
json_object_object_add(root, "is_running", json_object_new_boolean(res->is_running));
json_object_object_add(root, "timed_out", json_object_new_boolean(res->timed_out));
if (!res->is_running) {
json_object_object_add(root, "exit_status", json_object_new_int(res->exit_status));
}
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
r_process_result_free(res);
return out_str;
}
static char *python_status_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *pid_obj;
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
int pid = json_object_get_int(pid_obj);
int status;
bool running = true;
int exit_status = -1;
pid_t ret = waitpid(pid, &status, WNOHANG);
if (ret == pid) {
running = false;
exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
} else if (ret == -1) {
running = (kill(pid, 0) == 0);
}
FILE *fp = fdopen(fd, "w");
if (!fp) {
close(fd);
unlink(tmp_file);
return strdup("Failed to open temporary file for writing.");
char log_path[256];
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
char *content = NULL;
FILE *f = fopen(log_path, "r");
if (f) {
fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
if (size >= 0) {
content = malloc((size_t)size + 1);
if (content) {
size_t rs = fread(content, 1, (size_t)size, f);
content[rs] = '\0';
}
}
fclose(f);
}
fwrite(source_code, 1, strlen(source_code), fp);
fclose(fp);
struct json_object *root = json_object_new_object();
json_object_object_add(root, "pid", json_object_new_int(pid));
json_object_object_add(root, "is_running", json_object_new_boolean(running));
json_object_object_add(root, "output", json_object_new_string(content ? content : ""));
if (!running) {
json_object_object_add(root, "exit_status", json_object_new_int(exit_status));
}
if (content && *content) {
char *copy = strdup(content);
char *saveptr;
char *line = strtok_r(copy, "\n", &saveptr);
while (line) {
fprintf(stdout, "[%d]\t %s\n", pid, line);
line = strtok_r(NULL, "\n", &saveptr);
}
fflush(stdout);
free(copy);
}
char command[4096];
snprintf(command, sizeof(command), "python3 '%s'", tmp_file);
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
free(content);
return out_str;
}
output = r_bash_execute(command, false, 300);
static char *python_terminate_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *pid_obj;
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
int pid = json_object_get_int(pid_obj);
unlink(tmp_file);
return output;
kill(pid, SIGTERM);
usleep(100000);
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
char log_path[256];
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
unlink(log_path);
return strdup("Python process terminated and logs cleaned up.");
}
static struct json_object *python_execute_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("python_execute"));
json_object_object_add(f, "description", json_object_new_string("Execute Python code. If async is true, returns PID immediately."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *src = json_object_new_object();
json_object_object_add(src, "type", json_object_new_string("string"));
json_object_object_add(props, "source", src);
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name", json_object_new_string("python_execute"));
json_object_object_add(function, "description",
json_object_new_string("Executes Python source code using the python3 interpreter and returns stdout/stderr."));
struct json_object *to = json_object_new_object();
json_object_object_add(to, "type", json_object_new_string("integer"));
json_object_object_add(props, "timeout", to);
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *as = json_object_new_object();
json_object_object_add(as, "type", json_object_new_string("boolean"));
json_object_object_add(props, "async", as);
struct json_object *properties = json_object_new_object();
struct json_object *source = json_object_new_object();
json_object_object_add(source, "type", json_object_new_string("string"));
json_object_object_add(source, "description",
json_object_new_string("Python source code to execute."));
json_object_object_add(properties, "source", source);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("source"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("source"));
json_object_array_add(req, json_object_new_string("timeout"));
json_object_array_add(req, json_object_new_string("async"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
}
static struct json_object *python_status_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("python_get_status"));
json_object_object_add(f, "description", json_object_new_string("Get status and logs of a background Python process by PID."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *pid = json_object_new_object();
json_object_object_add(pid, "type", json_object_new_string("integer"));
json_object_object_add(props, "pid", pid);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("pid"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *python_terminate_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("python_terminate"));
json_object_object_add(f, "description", json_object_new_string("Terminate a background Python process and clean up."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *pid = json_object_new_object();
json_object_object_add(pid, "type", json_object_new_string("integer"));
json_object_object_add(props, "pid", pid);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("pid"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
+231 -105
View File
@@ -6,6 +6,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
static struct json_object *terminal_get_description(void);
static char *terminal_execute(tool_t *self, struct json_object *args);
@@ -14,6 +18,12 @@ static void terminal_print_action(const char *name, struct json_object *args);
static struct json_object *terminal_interactive_get_description(void);
static char *terminal_interactive_execute(tool_t *self, struct json_object *args);
static struct json_object *terminal_status_get_description(void);
static char *terminal_status_execute(tool_t *self, struct json_object *args);
static struct json_object *terminal_terminate_get_description(void);
static char *terminal_terminate_execute(tool_t *self, struct json_object *args);
static const tool_vtable_t terminal_vtable = {
.get_description = terminal_get_description,
.execute = terminal_execute,
@@ -26,23 +36,27 @@ static const tool_vtable_t terminal_interactive_vtable = {
.print_action = terminal_print_action
};
static tool_t terminal_tool = {
.vtable = &terminal_vtable,
.name = "linux_terminal_execute"
static const tool_vtable_t terminal_status_vtable = {
.get_description = terminal_status_get_description,
.execute = terminal_status_execute,
.print_action = NULL
};
static tool_t terminal_interactive_tool = {
.vtable = &terminal_interactive_vtable,
.name = "linux_terminal_execute_interactive"
static const tool_vtable_t terminal_terminate_vtable = {
.get_description = terminal_terminate_get_description,
.execute = terminal_terminate_execute,
.print_action = NULL
};
tool_t *tool_terminal_create(void) {
return &terminal_tool;
}
static tool_t terminal_tool = { .vtable = &terminal_vtable, .name = "linux_terminal_execute" };
static tool_t terminal_interactive_tool = { .vtable = &terminal_interactive_vtable, .name = "linux_terminal_execute_interactive" };
static tool_t terminal_status_tool = { .vtable = &terminal_status_vtable, .name = "linux_terminal_get_status" };
static tool_t terminal_terminate_tool = { .vtable = &terminal_terminate_vtable, .name = "linux_terminal_terminate" };
tool_t *tool_terminal_interactive_create(void) {
return &terminal_interactive_tool;
}
tool_t *tool_terminal_create(void) { return &terminal_tool; }
tool_t *tool_terminal_interactive_create(void) { return &terminal_interactive_tool; }
tool_t *tool_terminal_get_status_create(void) { return &terminal_status_tool; }
tool_t *tool_terminal_terminate_create(void) { return &terminal_terminate_tool; }
static void terminal_print_action(const char *name, struct json_object *args) {
if (!args) {
@@ -56,136 +70,248 @@ static void terminal_print_action(const char *name, struct json_object *args) {
}
if (json_object_object_get_ex(args, "command", &cmd)) {
if (strcmp(name, "linux_terminal_execute_interactive") == 0) {
fprintf(stderr, " \033[1m-> Running interactive (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd));
} else {
fprintf(stderr, " \033[1m-> Running command (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd));
const char *command = json_object_get_string(cmd);
fprintf(stderr, " \033[1m-> %s (timeout %ds):\033[0m\n", name, timeout);
char *copy = strdup(command);
char *saveptr;
char *line = strtok_r(copy, "\n", &saveptr);
while (line) {
fprintf(stderr, " \033[2m%s\033[0m\n", line);
line = strtok_r(NULL, "\n", &saveptr);
}
free(copy);
}
}
static char *terminal_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *cmd_obj, *timeout_obj;
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
return strdup("Error: missing 'command' argument");
}
struct json_object *cmd_obj, *timeout_obj, *async_obj;
if (!json_object_object_get_ex(args, "command", &cmd_obj)) return strdup("Error: missing 'command'");
int timeout = 300;
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) {
timeout = json_object_get_int(timeout_obj);
}
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
bool async = false;
if (json_object_object_get_ex(args, "async", &async_obj)) async = json_object_get_boolean(async_obj);
const char *command = json_object_get_string(cmd_obj);
return r_bash_execute(command, false, timeout);
r_process_result_t *res = r_bash_execute_ext(command, timeout, async);
struct json_object *root = json_object_new_object();
json_object_object_add(root, "pid", json_object_new_int(res->pid));
json_object_object_add(root, "output", json_object_new_string(res->output));
json_object_object_add(root, "is_running", json_object_new_boolean(res->is_running));
json_object_object_add(root, "timed_out", json_object_new_boolean(res->timed_out));
if (!res->is_running) {
json_object_object_add(root, "exit_status", json_object_new_int(res->exit_status));
}
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
r_process_result_free(res);
return out_str;
}
static char *terminal_interactive_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *cmd_obj, *timeout_obj;
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
return strdup("Error: missing 'command' argument");
}
if (!json_object_object_get_ex(args, "command", &cmd_obj)) return strdup("Error: missing 'command'");
int timeout = 300;
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) {
timeout = json_object_get_int(timeout_obj);
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
return r_bash_execute(json_object_get_string(cmd_obj), true, timeout);
}
static char *terminal_status_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *pid_obj;
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
int pid = json_object_get_int(pid_obj);
int status;
bool running = true;
int exit_status = -1;
pid_t ret = waitpid(pid, &status, WNOHANG);
if (ret == pid) {
running = false;
exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
} else if (ret == -1) {
// Not a child or already reaped
running = (kill(pid, 0) == 0);
}
const char *command = json_object_get_string(cmd_obj);
return r_bash_execute(command, true, timeout);
char log_path[256];
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
char *content = NULL;
FILE *f = fopen(log_path, "r");
if (f) {
fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
if (size >= 0) {
content = malloc((size_t)size + 1);
if (content) {
size_t rs = fread(content, 1, (size_t)size, f);
content[rs] = '\0';
}
}
fclose(f);
}
struct json_object *root = json_object_new_object();
json_object_object_add(root, "pid", json_object_new_int(pid));
json_object_object_add(root, "is_running", json_object_new_boolean(running));
json_object_object_add(root, "output", json_object_new_string(content ? content : ""));
if (!running) {
json_object_object_add(root, "exit_status", json_object_new_int(exit_status));
}
if (content && *content) {
char *copy = strdup(content);
char *saveptr;
char *line = strtok_r(copy, "\n", &saveptr);
while (line) {
fprintf(stdout, "[%d]\t %s\n", pid, line);
line = strtok_r(NULL, "\n", &saveptr);
}
fflush(stdout);
free(copy);
}
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
free(content);
return out_str;
}
static char *terminal_terminate_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *pid_obj;
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
int pid = json_object_get_int(pid_obj);
kill(pid, SIGTERM);
usleep(100000);
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
char log_path[256];
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
unlink(log_path);
return strdup("Process terminated and logs cleaned up.");
}
static struct json_object *terminal_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name",
json_object_new_string("linux_terminal_execute"));
json_object_object_add(function, "description",
json_object_new_string("Execute a linux_terminal command on user terminal with a timeout."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("linux_terminal_execute"));
json_object_object_add(f, "description", json_object_new_string("Execute a command. If async is true, returns immediately with PID."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *cmd = json_object_new_object();
json_object_object_add(cmd, "type", json_object_new_string("string"));
json_object_object_add(cmd, "description",
json_object_new_string("Bash command to execute."));
json_object_object_add(properties, "command", cmd);
json_object_object_add(props, "command", cmd);
struct json_object *timeout = json_object_new_object();
json_object_object_add(timeout, "type", json_object_new_string("integer"));
json_object_object_add(timeout, "description",
json_object_new_string("Timeout in seconds (default 300)."));
json_object_object_add(timeout, "default", json_object_new_int(300));
json_object_object_add(properties, "timeout", timeout);
struct json_object *to = json_object_new_object();
json_object_object_add(to, "type", json_object_new_string("integer"));
json_object_object_add(props, "timeout", to);
json_object_object_add(parameters, "properties", properties);
struct json_object *as = json_object_new_object();
json_object_object_add(as, "type", json_object_new_string("boolean"));
json_object_object_add(props, "async", as);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("command"));
json_object_array_add(required, json_object_new_string("timeout"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("command"));
json_object_array_add(req, json_object_new_string("timeout"));
json_object_array_add(req, json_object_new_string("async"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *terminal_interactive_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *function = json_object_new_object();
json_object_object_add(function, "name",
json_object_new_string("linux_terminal_execute_interactive"));
json_object_object_add(function, "description",
json_object_new_string("Executes interactive terminal for user with a timeout. You will not be able to read the result. Do not use if you need to know output."));
struct json_object *parameters = json_object_new_object();
json_object_object_add(parameters, "type", json_object_new_string("object"));
struct json_object *properties = json_object_new_object();
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("linux_terminal_execute_interactive"));
json_object_object_add(f, "description", json_object_new_string("Execute interactive command (vim, top)."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *cmd = json_object_new_object();
json_object_object_add(cmd, "type", json_object_new_string("string"));
json_object_object_add(cmd, "description",
json_object_new_string("Executable with parameters to execute interactively."));
json_object_object_add(properties, "command", cmd);
struct json_object *timeout = json_object_new_object();
json_object_object_add(timeout, "type", json_object_new_string("integer"));
json_object_object_add(timeout, "description",
json_object_new_string("Timeout in seconds (default 300)."));
json_object_object_add(timeout, "default", json_object_new_int(300));
json_object_object_add(properties, "timeout", timeout);
json_object_object_add(parameters, "properties", properties);
struct json_object *required = json_object_new_array();
json_object_array_add(required, json_object_new_string("command"));
json_object_array_add(required, json_object_new_string("timeout"));
json_object_object_add(parameters, "required", required);
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(function, "parameters", parameters);
json_object_object_add(props, "command", cmd);
struct json_object *to = json_object_new_object();
json_object_object_add(to, "type", json_object_new_string("integer"));
json_object_object_add(props, "timeout", to);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("command"));
json_object_array_add(req, json_object_new_string("timeout"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) {
json_object_object_add(function, "strict", json_object_new_boolean(1));
}
json_object_object_add(root, "function", function);
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
}
static struct json_object *terminal_status_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("linux_terminal_get_status"));
json_object_object_add(f, "description", json_object_new_string("Get status and logs of a background process by PID."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *pid = json_object_new_object();
json_object_object_add(pid, "type", json_object_new_string("integer"));
json_object_object_add(props, "pid", pid);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("pid"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *terminal_terminate_get_description(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("linux_terminal_terminate"));
json_object_object_add(f, "description", json_object_new_string("Terminate a background process and clean up."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *pid = json_object_new_object();
json_object_object_add(pid, "type", json_object_new_string("integer"));
json_object_object_add(props, "pid", pid);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("pid"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
+8
View File
@@ -5,6 +5,8 @@
extern tool_t *tool_terminal_create(void);
extern tool_t *tool_terminal_interactive_create(void);
extern tool_t *tool_terminal_get_status_create(void);
extern tool_t *tool_terminal_terminate_create(void);
extern tool_t *tool_read_file_create(void);
extern tool_t *tool_write_file_create(void);
extern tool_t *tool_directory_glob_create(void);
@@ -18,6 +20,8 @@ extern tool_t *tool_db_get_create(void);
extern tool_t *tool_db_set_create(void);
extern tool_t *tool_db_query_create(void);
extern tool_t *tool_python_execute_create(void);
extern tool_t *tool_python_get_status_create(void);
extern tool_t *tool_python_terminate_create(void);
extern tool_t *tool_index_source_directory_create(void);
extern tool_t *tool_code_grep_create(void);
extern tool_t *tool_code_symbol_find_create(void);
@@ -42,6 +46,8 @@ tool_registry_t *tools_get_registry(void) {
tool_registry_register(global_registry, tool_terminal_create());
tool_registry_register(global_registry, tool_terminal_interactive_create());
tool_registry_register(global_registry, tool_terminal_get_status_create());
tool_registry_register(global_registry, tool_terminal_terminate_create());
tool_registry_register(global_registry, tool_read_file_create());
tool_registry_register(global_registry, tool_write_file_create());
tool_registry_register(global_registry, tool_directory_glob_create());
@@ -55,6 +61,8 @@ tool_registry_t *tools_get_registry(void) {
tool_registry_register(global_registry, tool_db_set_create());
tool_registry_register(global_registry, tool_db_query_create());
tool_registry_register(global_registry, tool_python_execute_create());
tool_registry_register(global_registry, tool_python_get_status_create());
tool_registry_register(global_registry, tool_python_terminate_create());
tool_registry_register(global_registry, tool_index_source_directory_create());
// New tools