Compare commits

...
12 Commits
19 changed files with 886 additions and 269 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
recursive-include r2ai/ui *.tcss
+1 -1
View File
@@ -1 +1 @@
1.2.0
1.2.2
+4 -3
View File
@@ -1,6 +1,6 @@
(function () {
// Constants and configuration
const VERSION = "1.2.0";
const VERSION = "1.2.2";
const COMMAND = "decai";
const DEFAULT_PROMPT =
"Transform this pseudocode and respond ONLY with plain code (NO explanations, comments or markdown), Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:', Reduce lines of code and fit everything in a single function, Remove all dead code";
@@ -728,8 +728,9 @@ Use radare2 to resolve user requests.
"x-api-key: " + key[0],
];
let res = {};
try {
const res = http.post(
res = http.post(
provider.defaultBaseurl + "/v1/messages",
headers,
JSON.stringify(payload),
@@ -766,7 +767,7 @@ Use radare2 to resolve user requests.
try {
const res = http.post(url, [], JSON.stringify(payload));
if (res.error) {
if (res && res.error) {
throw new Error(res.error);
}
if (res.message && res.message.content) {
+10
View File
@@ -0,0 +1,10 @@
---
description: 'Decompile this code into PHP'
version: 0.1
author: pancake
command: pdg
if-empty: exit
---
Your goal is to $(-e r2ai.prompt).
Translate the code into $(-e r2ai.lang) programming language.
+9
View File
@@ -14,6 +14,15 @@
- Do not define variables inside the `for` parenthesis
- Do not check for null before calling free
## Test
Run `r2ai` cli tool or radare2 oneliners running the `r2ai` command.
* `r2pm -r r2ai -h` -> help message for the r2ai tool
* `r2 -qc 'r2ai -h' /bin/ls -> help message of the r2ai plugin
* `r2 -qc 'r2ai -a what arch is used here' -> testing auto mode
* `r2 -e r2ai.model=gemma3:12b -e r2ai.auto.raw=true -qc 'r2ai -a what arch is used here' -> testing auto mode
## Actions
Run the following commands to perform the action described
+5
View File
@@ -5,6 +5,7 @@ DOTLIB=.$(shell r2 -NNH R2_LIBEXT)
R2SYSPLUGDIR=$(shell r2 -NNH R2_LIBR_PLUGINS)
R2USRPLUGDIR=$(shell r2 -NNH R2_USER_PLUGINS)
R2BINDIR=$(shell r2pm -H R2PM_BINDIR)
R2MANDIR=$(shell r2pm -H R2PM_MANDIR)
R2_CFLAGS=$(shell pkg-config --cflags r_core)
R2_LDFLAGS=$(shell pkg-config --libs r_core)
@@ -48,6 +49,7 @@ OBJS+=r2ai_http.o
OBJS+=openai.o
OBJS+=markdown.o
OBJS+=query.o
OBJS+=rawtools.o
all: $(OBJS) r2check
$(CC) -fPIC -shared -o r2ai$(DOTLIB) $(OBJS) $(CFLAGS) $(LDFLAGS)
@@ -69,15 +71,18 @@ fmt format indent:
user-install: user-uninstall
mkdir -p $(R2USRPLUGDIR)
mkdir -p $(R2BINDIR)
mkdir -p $(R2MANDIR)/man1
mkdir -p $(HOME)/.config/r2ai/prompts
$(MAKE) user-uninstall
cp -f r2ai$(DOTLIB) $(R2USRPLUGDIR)/r2ai$(DOTLIB)
cp -f r2ai$(DOTEXE) $(R2BINDIR)/r2ai$(DOTEXE)
cp -f r2ai.1 $(R2MANDIR)/man1/r2ai.1
cp -r ../prompts/* $(HOME)/.config/r2ai/prompts/
user-uninstall:
rm -f $(R2USRPLUGDIR)/r2ai$(DOTLIB)
rm -f $(R2BINDIR)/r2ai$(DOTEXE)
rm -f $(R2MANDIR)/man1/r2ai.1
install:
$(MAKE) uninstall
+100 -49
View File
@@ -62,7 +62,7 @@ static void r2ai_print_run_end(RCorePluginSession *cps, const R2AI_Usage *usage,
char *total_time_str = format_time_duration (total_time);
// Print detailed stats
R2_PRINTF ("\x1b[1;34m%s | total: %d in: %d out: %d | run: %d in: %d out: %d | %s / %s\x1b[0m\n",
r_cons_printf (core->cons, "\x1b[1;34m%s | total: %d in: %d out: %d | run: %d in: %d out: %d | %s / %s\x1b[0m\n",
r_config_get (core->config, "r2ai.model"),
state->stats.total_tokens,
state->stats.total_prompt_tokens,
@@ -72,8 +72,8 @@ static void r2ai_print_run_end(RCorePluginSession *cps, const R2AI_Usage *usage,
state->stats.run_completion_tokens,
run_time_str,
total_time_str);
R2_NEWLINE ();
R2_FLUSH ();
r_cons_newline (core->cons);
r_cons_flush (core->cons);
free (run_time_str);
free (total_time_str);
@@ -108,38 +108,43 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
const int max_runs = r_config_get_i (core->config, "r2ai.auto.max_runs");
if (n_run > max_runs) {
R2_PRINTF ("\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
R2_FLUSH ();
r_cons_printf (core->cons, "\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
r_cons_flush (core->cons);
return;
}
if (!system_prompt) {
system_prompt = Gprompt_auto;
const char *init_commands = r_config_get (core->config, "r2ai.auto.init_commands");
if (R_STR_ISNOTEMPTY (init_commands)) {
char *edited_command = NULL;
char *cmd_output = execute_tool (core, "r2cmd", r_str_newf ("{\"command\":\"%s\"}", init_commands), &edited_command);
if (cmd_output) {
system_prompt = r_str_newf ("%s\n\nHere is some information about the binary to get you started:\n>%s\n%s", Gprompt_auto, edited_command, cmd_output);
if (R_STR_ISNOTEMPTY (cmd_output)) {
R2AI_Message init_msg = {
.role = "system",
.content = r_str_newf ("Here is some information about the binary to get you started:\n>%s\n%s", edited_command, cmd_output)
};
r2ai_msgs_add (messages, &init_msg);
free (cmd_output);
}
free (edited_command);
} else {
system_prompt = Gprompt_auto;
}
}
r2ai_stats_init_run (state, n_run);
r_cons_printf (core->cons, "\x1b[35m[DEBUG] About to call r2ai_llmcall with n_run=%d\x1b[0m\n", n_run);
r_cons_flush (core->cons);
// Set up args for r2ai_llmcall call with tools directly
R2AIArgs args = {
.messages = messages,
.error = &error,
.dorag = true,
.tools = r2ai_get_tools (),
.tools = r2ai_get_tools (), // Always send tools in auto mode
.system_prompt = system_prompt
};
// Call r2ai_llmcall to get a response
R2AI_ChatResponse *response = r2ai_llmcall (cps, args);
if (!response) {
@@ -155,18 +160,34 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
return;
}
// Debug logging for rawtools
R_LOG_DEBUG ("[DEBUG] Model response - Role: %s", message->role? message->role: "null");
if (message->content) {
R_LOG_DEBUG ("[DEBUG] Content: %s", message->content);
}
if (message->reasoning_content) {
R_LOG_DEBUG ("[DEBUG] Reasoning: %s", message->reasoning_content);
}
if (message->tool_calls && message->n_tool_calls > 0) {
R_LOG_DEBUG ("[DEBUG] Tool calls: %d", message->n_tool_calls);
for (int i = 0; i < message->n_tool_calls; i++) {
const R2AI_ToolCall *tc = &message->tool_calls[i];
R_LOG_DEBUG ("[DEBUG] Tool %d: %s - %s", i, tc->name? tc->name: "null", tc->arguments? tc->arguments: "null");
}
}
// Process the response - we need to add it to our messages array
if (message->content || message->reasoning_content) {
R2_PRINTF ("\x1b[31m[Assistant]\x1b[0m\n\n");
r_cons_printf (core->cons, "\x1b[31m[Assistant]\x1b[0m\n\n");
if (message->reasoning_content) {
R2_PRINTF ("\x1b[90m<thinking>\n%s\n</thinking>\x1b[0m\n", message->reasoning_content);
R2_NEWLINE ();
R2_FLUSH ();
r_cons_printf (core->cons, "\x1b[90m<thinking>\n%s\n</thinking>\x1b[0m\n", message->reasoning_content);
r_cons_newline (core->cons);
r_cons_flush (core->cons);
}
if (message->content) {
R2_PRINTF ("%s", message->content);
R2_NEWLINE ();
R2_FLUSH ();
r_cons_printf (core->cons, "%s", message->content);
r_cons_newline (core->cons);
r_cons_flush (core->cons);
}
}
@@ -176,16 +197,17 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
// Check for tool calls and process them
if (message->tool_calls && message->n_tool_calls > 0) {
R_LOG_DEBUG ("Found %d tool call (s)", message->n_tool_calls);
R_LOG_DEBUG ("Found %d tool call(s)", message->n_tool_calls);
// Process each tool call
for (int i = 0; i < message->n_tool_calls; i++) {
const R2AI_ToolCall *tool_call = &message->tool_calls[i];
if (!tool_call->name || !tool_call->arguments || !tool_call->id) {
R_LOG_DEBUG ("Skipping invalid tool call %d", i);
continue;
}
R_LOG_DEBUG ("Tool call: %s", tool_call->name);
R_LOG_DEBUG ("Tool call %d: %s with args: %s", i, tool_call->name, tool_call->arguments);
// Don't log the full arguments which might get truncated
char *tool_name = strdup (tool_call->name);
char *tool_args = strdup (tool_call->arguments);
@@ -202,15 +224,46 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
} else {
char *edited_command = NULL;
cmd_output = execute_tool (core, tool_name, tool_args, &edited_command);
// TODO: need to edit the R2AI_Messages* and modify the command of the last tool_use
if (edited_command) {
// Update the last message's tool call arguments with the edited command
R2AI_Message *last_msg = r_list_get_n (messages->messages, r_list_length (messages->messages) - 1);
if (last_msg && last_msg->tool_calls && last_msg->n_tool_calls > 0) {
for (int j = 0; j < last_msg->n_tool_calls; j++) {
R2AI_ToolCall *tc = (R2AI_ToolCall *)&last_msg->tool_calls[j];
if (tc->id && strcmp (tc->id, tool_call->id) == 0) {
// For r2cmd, update the command in arguments
if (strcmp (tool_name, "r2cmd") == 0) {
char *args_dup = strdup (tc->arguments);
RJson *args_json = r_json_parse (args_dup);
if (args_json) {
RJson *cmd_json = (RJson *)r_json_get (args_json, "command");
if (cmd_json && cmd_json->str_value) {
// Update the command field
free ((char *)cmd_json->str_value);
cmd_json->str_value = strdup (edited_command);
// Serialize back to JSON
char *new_args = r_json_to_string (args_json);
if (new_args) {
free ((void *)tc->arguments);
tc->arguments = new_args;
}
}
r_json_free (args_json);
}
}
break;
}
}
}
}
free (edited_command);
}
free (tool_name);
free (tool_args);
if (strcmp (cmd_output, "R2AI_SIGINT") == 0) {
R2_PRINTF ("\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
R2_FLUSH ();
r_cons_printf (core->cons, "\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
r_cons_flush (core->cons);
free (cmd_output);
cmd_output = strdup ("<user interrupted>");
interrupted = true;
@@ -225,6 +278,8 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
// Add the tool response to our messages array
r2ai_msgs_add (messages, &tool_response);
R_LOG_DEBUG ("[DEBUG] Added tool response to messages: %s", cmd_output? cmd_output: "null");
r_cons_printf (core->cons, "\x1b[32m[DEBUG] Tool result: %s\x1b[0m\n", cmd_output? cmd_output: "no output");
free (cmd_output);
}
@@ -232,7 +287,10 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
// Check if we should continue with recursion
if (!interrupted && message->tool_calls && message->n_tool_calls > 0) {
R_LOG_DEBUG ("[DEBUG] Recursing to process_messages with n_run=%d", n_run + 1);
process_messages (cps, messages, system_prompt, n_run + 1);
} else {
R_LOG_DEBUG ("[DEBUG] Auto mode loop ending - no more tool calls or interrupted");
}
} else {
r2ai_print_run_end (cps, usage, n_run, max_runs);
@@ -245,6 +303,8 @@ R_API void process_messages(RCorePluginSession *cps, R2AI_Messages *messages, co
R_IPI void cmd_r2ai_a(RCorePluginSession *cps, const char *user_query) {
RCore *core = cps->core;
R2AI_State *state = cps->data;
r_cons_printf (core->cons, "\x1b[35m[DEBUG] cmd_r2ai_a called with query: %s\x1b[0m\n", user_query);
r_cons_flush (core->cons);
// Get conversation
R2AI_Messages *messages = r2ai_conversation_get (state);
if (!messages) {
@@ -261,7 +321,7 @@ R_IPI void cmd_r2ai_a(RCorePluginSession *cps, const char *user_query) {
// Add user query
R2AI_Message user_msg = {
.role = "user",
.content = user_query
.content = (char *)user_query
};
r2ai_msgs_add (messages, &user_msg);
@@ -271,7 +331,7 @@ R_IPI void cmd_r2ai_a(RCorePluginSession *cps, const char *user_query) {
// Helper function to display content with length indication for long content
static void print_content_with_length(RCore *core, const char *content, const char *empty_msg, bool always_show_length) {
if (!content || *content == '\0') {
R2_PRINTF ("%s\n", empty_msg? empty_msg: "<no content>");
r_cons_printf (core->cons, "%s\n", empty_msg? empty_msg: "<no content>");
return;
}
@@ -281,15 +341,15 @@ static void print_content_with_length(RCore *core, const char *content, const ch
if (content_len > max_display) {
// Truncate long content and show length
char *truncated = r_str_ndup (content, max_display);
R2_PRINTF ("%s... \x1b[1;37m(length: %zu chars)\x1b[0m\n",
r_cons_printf (core->cons, "%s... \x1b[1;37m(length: %zu chars)\x1b[0m\n",
truncated, content_len);
free (truncated);
} else if (always_show_length) {
// Always show length for certain types (like tool responses)
R2_PRINTF ("%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
r_cons_printf (core->cons, "%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
content, content_len);
} else {
R2_PRINTF ("%s\n", content);
r_cons_printf (core->cons, "%s\n", content);
}
}
@@ -300,7 +360,7 @@ R_IPI void cmd_r2ai_logs(RCorePluginSession *cps) {
// Get conversation
R2AI_Messages *messages = r2ai_conversation_get (state);
if (!messages || r_list_length (messages->messages) == 0) {
R2_PRINTF ("No conversation history available\n");
r_cons_printf (core->cons, "No conversation history available\n");
return;
}
@@ -318,49 +378,40 @@ R_IPI void cmd_r2ai_logs(RCorePluginSession *cps) {
for (int i = 0; i < r_list_length (messages->messages); i++) {
const R2AI_Message *msg = r_list_get_n (messages->messages, i);
pj_o (pj);
pj_ks (pj, "role", msg->role? msg->role: "unknown");
pj_ks (pj, "content", msg->content? msg->content: "");
if (msg->tool_calls && msg->n_tool_calls > 0) {
pj_ka (pj, "tool_calls");
for (int j = 0; j < msg->n_tool_calls; j++) {
const R2AI_ToolCall *tc = &msg->tool_calls[j];
pj_o (pj);
if (tc->name) {
pj_ks (pj, "name", tc->name);
}
if (tc->arguments) {
pj_ks (pj, "arguments", tc->arguments);
}
pj_end (pj);
}
pj_end (pj);
}
pj_end (pj);
}
pj_end (pj);
char *json_str = pj_drain (pj);
R2_PRINTF ("%s\n", json_str);
r_cons_printf (core->cons, "%s\n", json_str);
free (json_str);
return;
}
R2_PRINTF ("\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
r_cons_printf (core->cons, "\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
r_list_length (messages->messages));
R2_PRINTF ("\x1b[1;33mNote: System prompt is applied automatically but not stored in history\x1b[0m\n\n");
r_cons_printf (core->cons, "\x1b[1;33mNote: System prompt is applied automatically but not stored in history\x1b[0m\n\n");
// Display each message in the conversation
for (int i = 0; i < r_list_length (messages->messages); i++) {
@@ -369,35 +420,35 @@ R_IPI void cmd_r2ai_logs(RCorePluginSession *cps) {
// Format based on role
if (!strcmp (role, "user")) {
R2_PRINTF ("\x1b[1;32m[user]:\x1b[0m ");
r_cons_printf (core->cons, "\x1b[1;32m[user]:\x1b[0m ");
print_content_with_length (core, msg->content, "<no content>", false);
} else if (!strcmp (role, "assistant")) {
R2_PRINTF ("\x1b[1;36m[assistant]:\x1b[0m ");
r_cons_printf (core->cons, "\x1b[1;36m[assistant]:\x1b[0m ");
print_content_with_length (core, msg->content, "<no content>", false);
// Show tool calls if present
if (msg->tool_calls && msg->n_tool_calls > 0) {
for (int j = 0; j < msg->n_tool_calls; j++) {
const R2AI_ToolCall *tc = &msg->tool_calls[j];
R2_PRINTF (" \x1b[1;35m[tool call]:\x1b[0m %s\n",
r_cons_printf (core->cons, " \x1b[1;35m[tool call]:\x1b[0m %s\n",
tc->name? tc->name: "<unnamed>");
if (tc->arguments) {
R2_PRINTF (" %s\n", tc->arguments);
r_cons_printf (core->cons, " %s\n", tc->arguments);
}
}
}
} else if (!strcmp (role, "tool")) {
R2_PRINTF ("\x1b[1;35m[tool]:\x1b[0m ");
r_cons_printf (core->cons, "\x1b[1;35m[tool]:\x1b[0m ");
print_content_with_length (core, msg->content, "<no result>", true);
// Don't show the tool call ID as requested
} else {
// Other roles (system, etc.)
R2_PRINTF ("\x1b[1;37m[%s]:\x1b[0m ", role);
r_cons_printf (core->cons, "\x1b[1;37m[%s]:\x1b[0m ", role);
print_content_with_length (core, msg->content, "<no content>", false);
}
R2_NEWLINE ();
R2_FLUSH ();
r_cons_newline (core->cons);
r_cons_flush (core->cons);
}
}
+15 -4
View File
@@ -18,6 +18,9 @@ static const R2AIProvider r2ai_providers[] = {
};
R_IPI const R2AIProvider *r2ai_get_provider(const char *name) {
if (R_STR_ISEMPTY (name)) {
return NULL;
}
for (int i = 0; r2ai_providers[i].name; i++) {
if (!strcmp (name, r2ai_providers[i].name)) {
return &r2ai_providers[i];
@@ -26,14 +29,21 @@ R_IPI const R2AIProvider *r2ai_get_provider(const char *name) {
return NULL;
}
// Forward declaration for rawtools
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCorePluginSession *cps, R2AIArgs args) {
RCore *core = cps->core;
R2AI_State *state = cps->data;
R2AI_ChatResponse *res = NULL;
// Check if rawtools mode is enabled
bool rawtools_enabled = r_config_get_b (core->config, "r2ai.auto.raw");
const char *provider = args.provider? args.provider: r_config_get (core->config, "r2ai.api");
if (!provider) {
provider = "gemini";
}
if (rawtools_enabled && args.tools && args.tools->n_tools > 0) {
return r2ai_rawtools_llmcall (cps, args);
}
R2AI_State *state = cps->data;
R2AI_ChatResponse *res = NULL;
if (!args.model) {
const char *config_model = r_config_get (core->config, "r2ai.model");
args.model = strdup (config_model? config_model: "");
@@ -113,7 +123,7 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCorePluginSession *cps, R2AIArgs args) {
// Add the rest of the messages one by one
if (!args.messages && args.input) {
R2AI_Message msg = { .role = "user", .content = args.input };
R2AI_Message msg = { .role = "user", .content = (char *)args.input };
args.messages = r2ai_msgs_new ();
r2ai_msgs_add (args.messages, &msg);
}
@@ -309,7 +319,7 @@ R_IPI void r2ai_list_providers(RCore *core, RStrBuf *sb) {
}
r_strbuf_append (sb, r2ai_providers[i].name);
} else {
R2_PRINTLN (r2ai_providers[i].name);
r_cons_println (core->cons, r2ai_providers[i].name);
}
}
}
@@ -337,6 +347,7 @@ R_IPI void r2ai_refresh_embeddings(RCorePluginSession *cps) {
char *filepath = r_file_new (path, file, NULL);
char *text = r_file_slurp (filepath, NULL);
if (text) {
R_LOG_DEBUG ("Index %s", file);
RList *lines = r_str_split_list (text, "\n", -1);
r_list_foreach (lines, iter2, line) {
if (r_str_trim_head_ro (line)[0] == 0) {
+22 -5
View File
@@ -3,12 +3,13 @@
#include "r2ai_priv.h"
static void show_help() {
printf ("Usage: r2ai [-vhp:m:q:] <prompt>\n"
printf ("Usage: r2ai [-vhp:m:q:E] <prompt>\n"
" -v Show version information\n"
" -h Show this help message\n"
" -p <provider> Select LLM provider\n"
" -m <model> Select LLM model\n"
" -q <query> Execute predefined prompt query (can be used multiple times)\n");
" -q <query> Execute predefined prompt query (can be used multiple times)\n"
" -E Edit the r2ai rc file\n");
}
static void show_version() {
@@ -22,7 +23,7 @@ int main(int argc, const char **argv) {
RList *queries = r_list_newf (free);
RGetopt opt;
r_getopt_init (&opt, argc, argv, "vhp:m:q:");
r_getopt_init (&opt, argc, argv, "vhp:m:q:E");
while ((c = r_getopt_next (&opt)) != -1) {
switch (c) {
case 'v':
@@ -42,6 +43,21 @@ int main(int argc, const char **argv) {
case 'q':
r_list_append (queries, strdup (opt.arg));
break;
case 'E':
{
RCore *core = r_core_new ();
RCorePluginSession cps = {
.core = core
};
r2ai_init (&cps);
char *rc_path = r_file_home (".config/r2ai/rc");
r_cons_editor (core->cons, rc_path, NULL);
free (rc_path);
r_list_free (queries);
r2ai_fini (&cps);
r_core_free (core);
return 0;
}
default:
show_help ();
r_list_free (queries);
@@ -64,11 +80,12 @@ int main(int argc, const char **argv) {
}
} else {
if (opt.ind >= argc) {
show_help ();
// List available prompts
r2ai_cmd_q (&cps, "");
r_list_free (queries);
r2ai_fini (&cps);
r_core_free (core);
return 1;
return 0;
}
const char *prompt = argv[opt.ind];
+2 -1
View File
@@ -1,5 +1,5 @@
project('r2ai', 'c',
version : '1.2.0',
version : '1.2.2',
default_options : ['warning_level=2', 'c_std=c11']
)
@@ -191,6 +191,7 @@ srcs = files(
'llm.c',
'tools.c',
'query.c',
'rawtools.c',
'messages.c',
'anthropic.c',
'r2ai_http.c',
-15
View File
@@ -725,18 +725,3 @@ R_API char *r_json_to_string(const RJson *json) {
PJ *pj = r_json_to_pj (json, NULL);
return pj? pj_drain (pj): NULL;
}
// Create a conversation with optional initial user message
R_API R2AI_Messages *create_conversation(const char *user_message) {
// Create a temporary message container (not using static storage)
R2AI_Messages *msgs = r2ai_msgs_new ();
// Add user message if provided (no system message - that's added during processing)
if (msgs && R_STR_ISNOTEMPTY (user_message)) {
R2AI_Message user_msg = {
.role = "user",
.content = user_message
};
r2ai_msgs_add (msgs, &user_msg);
}
return msgs;
}
+10 -11
View File
@@ -75,6 +75,8 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
if (!state->model_compat_db) {
state->model_compat_db = ht_pp_new0 ();
}
args.provider = r_config_get (core->config, "r2ai.api");
args.model = r_config_get (core->config, "r2ai.model");
const char *base_url = r2ai_get_provider_url (core, args.provider);
// TODO: default model name should depend on api
@@ -91,7 +93,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
}
R2AI_Message system_msg = {
.role = "system",
.content = args.system_prompt
.content = (char *)args.system_prompt
};
// Add system message if available from args.system_prompt
if (R_STR_ISNOTEMPTY (args.system_prompt)) {
@@ -99,10 +101,10 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
// if the model name contains "o1" or "o3", it's "developer" role
if (strstr (model_name, "o1") || strstr (model_name, "o3")) {
system_msg.role = "developer";
system_msg.content = args.system_prompt;
system_msg.content = (char *)args.system_prompt;
} else {
system_msg.role = "system";
system_msg.content = args.system_prompt;
system_msg.content = (char *)args.system_prompt;
}
r2ai_msgs_add (temp_msgs, &system_msg);
} else {
@@ -115,7 +117,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
} else {
system_msg.role = "system";
}
system_msg.content = sysprompt;
system_msg.content = (char *)sysprompt;
r2ai_msgs_add (temp_msgs, &system_msg);
}
}
@@ -144,7 +146,6 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
headers[0] = "Content-Type: application/json";
headers[1] = auth_header;
}
const char *urlfmt = strcmp (args.provider, "ollama")
? "%s/chat/completions"
: "%s/chat";
@@ -280,24 +281,23 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
R_LOG_ERROR ("OpenAI API error response: %s", res);
// Check for specific error types in the response
ModelErrorFlags error_flag = MODEL_ERROR_NONE;
const char *model_name = args.model? args.model: "gpt-5-mini";
// Check for temperature errors
if (strstr (res, "temperature")) {
R_LOG_DEBUG ("Detected temperature error for %s model %s", args.provider, model_name);
R_LOG_DEBUG ("Detected temperature error for %s model %s", args.provider, args.model);
error_flag |= MODEL_ERROR_TEMPERATURE;
}
if (error_flag != MODEL_ERROR_NONE) {
// Record the error flags for this provider/model
model_add_error (state, args.provider, model_name, error_flag);
model_add_error (state, args.provider, args.model, error_flag);
// Clean up
free (auth_header);
free (res);
// Retry the call (it will skip problematic parameters this time)
R_LOG_INFO ("Retrying request with adjusted parameters for %s/%s", args.provider, model_name);
R_LOG_INFO ("Retrying request with adjusted parameters for %s/%s", args.provider, args.model);
RCorePluginSession retry_cps = { .core = core, .data = state };
return r2ai_openai (&retry_cps, args);
}
@@ -394,8 +394,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCorePluginSession *cps, R2AIArgs args) {
if (reasoning_content && reasoning_content->type == R_JSON_STRING) {
message->reasoning_content = strdup (reasoning_content->str_value);
}
// TODO: Handle tool calls if present
// TODO: Handle tool calls if present?
}
}
r_json_free (jres);
+170 -59
View File
@@ -14,6 +14,8 @@ R_API void r2aiprompt_free(R2AIPrompt *prompt) {
free (prompt->requires);
free (prompt->if_empty);
free (prompt->if_command);
free (prompt->model);
free (prompt->provider);
free (prompt);
}
@@ -24,44 +26,92 @@ static R2AIPrompt *parse_prompt_file(const char *filepath) {
free (prompt);
return NULL;
}
RList *lines = r_str_split_list (content, "\n", -1);
RListIter *iter;
char *line;
r_list_foreach (lines, iter, line) {
r_str_trim (line);
if (*line == '#' || *line == 0) {
continue;
// Check if it's a markdown file with YAML frontmatter
if (r_str_startswith (content, "---\n")) {
// Parse YAML frontmatter
char *frontmatter_end = strstr (content + 4, "\n---\n");
if (frontmatter_end) {
char *frontmatter = r_str_ndup (content + 4, frontmatter_end - (content + 4));
RList *lines = r_str_split_list (frontmatter, "\n", -1);
RListIter *iter;
char *line;
r_list_foreach (lines, iter, line) {
r_str_trim (line);
if (*line == 0) {
continue;
}
char *colon = strchr (line, ':');
if (!colon) {
continue;
}
*colon = 0;
char *key = line;
char *value = colon + 1;
r_str_trim (key);
r_str_trim (value);
if (!strcmp (key, "title")) {
prompt->desc = strdup (value);
} else if (!strcmp (key, "author")) {
prompt->author = strdup (value);
} else if (!strcmp (key, "description")) {
prompt->title = strdup (value);
} else if (!strcmp (key, "command")) {
prompt->command = strdup (value);
} else if (!strcmp (key, "model")) {
prompt->model = strdup (value);
} else if (!strcmp (key, "provider")) {
prompt->provider = strdup (value);
}
}
r_list_free (lines);
free (frontmatter);
// The prompt content starts after the second ---
char *prompt_start = frontmatter_end + 5; // Skip "\n---\n"
prompt->prompt = strdup (prompt_start);
}
char *colon = strchr (line, ':');
if (!colon) {
continue;
}
*colon = 0;
char *key = line;
char *value = colon + 1;
r_str_trim (key);
r_str_trim (value);
if (!strcmp (key, "Title")) {
prompt->title = strdup (value);
} else if (!strcmp (key, "Author")) {
prompt->author = strdup (value);
} else if (!strcmp (key, "Description")) {
prompt->desc = strdup (value);
} else if (!strcmp (key, "Command") || !strcmp (key, "Commands")) {
prompt->command = strdup (value);
} else if (!strcmp (key, "Prompt") || !strcmp (key, "Query")) {
prompt->prompt = strdup (value);
} else if (!strcmp (key, "Depends")) {
prompt->
requires
= strdup (value);
} else if (!strcmp (key, "If-Empty")) {
prompt->if_empty = strdup (value);
} else if (!strcmp (key, "If-Command")) {
prompt->if_command = strdup (value);
} else {
// Original parsing for .txt files
RList *lines = r_str_split_list (content, "\n", -1);
RListIter *iter;
char *line;
r_list_foreach (lines, iter, line) {
r_str_trim (line);
if (*line == '#' || *line == 0) {
continue;
}
char *colon = strchr (line, ':');
if (!colon) {
continue;
}
*colon = 0;
char *key = line;
char *value = colon + 1;
r_str_trim (key);
r_str_trim (value);
if (!strcmp (key, "Title")) {
prompt->title = strdup (value);
} else if (!strcmp (key, "Author")) {
prompt->author = strdup (value);
} else if (!strcmp (key, "Description")) {
prompt->desc = strdup (value);
} else if (!strcmp (key, "Command") || !strcmp (key, "Commands")) {
prompt->command = strdup (value);
} else if (!strcmp (key, "Prompt") || !strcmp (key, "Query")) {
prompt->prompt = strdup (value);
} else if (!strcmp (key, "Depends")) {
prompt->
requires
= strdup (value);
} else if (!strcmp (key, "If-Empty")) {
prompt->if_empty = strdup (value);
} else if (!strcmp (key, "If-Command")) {
prompt->if_command = strdup (value);
}
}
r_list_free (lines);
}
r_list_free (lines);
free (content);
return prompt;
}
@@ -140,33 +190,61 @@ R_API void r2ai_cmd_q(RCorePluginSession *cps, const char *input) {
promptdir = "~/.config/r2ai/prompts";
}
char *expanded_dir = r_file_abspath (promptdir);
if (!r_file_is_directory (expanded_dir)) {
R_LOG_ERROR ("Prompt directory does not exist: %s", expanded_dir);
char *local_prompts = r_file_abspath ("../prompts");
RList *search_dirs = r_list_newf (free);
if (r_file_is_directory (expanded_dir)) {
r_list_append (search_dirs, expanded_dir);
} else {
free (expanded_dir);
expanded_dir = NULL;
}
if (r_file_is_directory (local_prompts)) {
r_list_append (search_dirs, local_prompts);
} else {
free (local_prompts);
local_prompts = NULL;
}
if (r_list_empty (search_dirs)) {
R_LOG_ERROR ("No prompt directories found");
r_list_free (search_dirs);
return;
}
if (!input || !*input) {
// list prompts
RList *files = r_sys_dir (expanded_dir);
RListIter *iter;
char *file;
r_list_foreach (files, iter, file) {
if (r_str_endswith (file, ".r2ai.txt")) {
char *dot = strchr (file, '.');
char *name = dot? r_str_ndup (file, dot - file): strdup (file);
char *filepath = r_str_newf ("%s/%s.r2ai.txt", expanded_dir, name);
R2AIPrompt *prompt = parse_prompt_file (filepath);
if (prompt) {
R2_PRINTF ("%s: %s - %s\n", name, prompt->title? prompt->title: "", prompt->desc? prompt->desc: "");
r2aiprompt_free (prompt);
} else {
R2_PRINTLN (name);
RListIter *dir_iter;
char *dir;
r_list_foreach (search_dirs, dir_iter, dir) {
RList *files = r_sys_dir (dir);
RListIter *iter;
char *file;
r_list_foreach (files, iter, file) {
if (r_str_endswith (file, ".r2ai.txt") || r_str_endswith (file, ".r2ai.md")) {
char *dot = strchr (file, '.');
char *name = dot? r_str_ndup (file, dot - file): strdup (file);
char *filepath_txt = r_str_newf ("%s/%s.r2ai.txt", dir, name);
char *filepath_md = r_str_newf ("%s/%s.r2ai.md", dir, name);
char *filepath = NULL;
if (r_file_exists (filepath_txt)) {
filepath = filepath_txt;
} else if (r_file_exists (filepath_md)) {
filepath = filepath_md;
}
if (filepath) {
R2AIPrompt *prompt = parse_prompt_file (filepath);
if (prompt) {
r_cons_printf (core->cons, "%s: %s - %s\n", name, prompt->title? prompt->title: "", prompt->desc? prompt->desc: "");
r2aiprompt_free (prompt);
} else {
r_cons_println (core->cons, name);
}
}
free (name);
free (filepath_txt);
free (filepath_md);
}
free (name);
free (filepath);
}
r_list_free (files);
}
r_list_free (files);
} else {
// run prompt
char *name = strdup (input);
@@ -176,11 +254,35 @@ R_API void r2ai_cmd_q(RCorePluginSession *cps, const char *input) {
r_str_trim (extra);
}
r_str_trim (name);
char *filepath = r_str_newf ("%s/%s.r2ai.txt", expanded_dir, name);
char *filepath = NULL;
RListIter *dir_iter;
char *dir;
r_list_foreach (search_dirs, dir_iter, dir) {
char *filepath_txt = r_str_newf ("%s/%s.r2ai.txt", dir, name);
char *filepath_md = r_str_newf ("%s/%s.r2ai.md", dir, name);
if (r_file_exists (filepath_txt)) {
filepath = filepath_txt;
free (filepath_md);
break;
} else if (r_file_exists (filepath_md)) {
filepath = filepath_md;
free (filepath_txt);
break;
}
free (filepath_txt);
free (filepath_md);
}
if (!filepath) {
R_LOG_ERROR ("Cannot find prompt file: %s.r2ai.txt or %s.r2ai.md", name, name);
r_list_free (search_dirs);
free (name);
return;
}
R2AIPrompt *prompt = parse_prompt_file (filepath);
if (!prompt) {
R_LOG_ERROR ("Cannot read prompt file: %s", filepath);
free (filepath);
r_list_free (search_dirs);
free (name);
return;
}
@@ -203,7 +305,16 @@ R_API void r2ai_cmd_q(RCorePluginSession *cps, const char *input) {
// determine the prompt to use
char *use_prompt = prompt->prompt;
if ((!cmd_output || !*cmd_output) && prompt->if_empty) {
use_prompt = prompt->if_empty;
if (!strcmp (prompt->if_empty, "exit")) {
R_LOG_ERROR ("Command output is empty, exiting prompt");
r2aiprompt_free (prompt);
free (cmd_output);
free (name);
r_list_free (search_dirs);
return;
} else {
use_prompt = prompt->if_empty;
}
}
// replace vars in prompt
char *replaced_prompt = replace_vars (core, use_prompt);
@@ -219,17 +330,17 @@ R_API void r2ai_cmd_q(RCorePluginSession *cps, const char *input) {
char *final_prompt = r_strbuf_drain (sb);
// now send to LLM
char *error = NULL;
char *res = r2ai (cps, (R2AIArgs){ .input = final_prompt, .error = &error, .dorag = true });
char *res = r2ai (cps, (R2AIArgs){ .input = final_prompt, .model = prompt->model, .provider = prompt->provider, .error = &error, .dorag = true });
if (error) {
R_LOG_ERROR ("%s", error);
free (error);
} else if (res) {
R2_PRINTF ("%s\n", res);
r_cons_printf (core->cons, "%s\n", res);
free (res);
}
r2aiprompt_free (prompt);
free (final_prompt);
free (name);
}
free (expanded_dir);
r_list_free (search_dirs);
}
+158
View File
@@ -0,0 +1,158 @@
.TH R2AI 1 "October 2025" "radare2" "User Commands"
.SH NAME
r2ai \- AI assistant for radare2
.SH SYNOPSIS
.B r2ai
[\fIoptions\fR] [\fIquery\fR]
.SH DESCRIPTION
r2ai is an AI-powered plugin and command-line tool for radare2 that provides assistance with reverse engineering tasks, including decompilation, function analysis, vulnerability detection, and more. It integrates with various LLM providers like OpenAI, Anthropic, and Ollama.
.SH OPTIONS
.TP
.B \-d
Decompile the current function.
.TP
.B \-d \fIquery\fR
Ask a question about the current function.
.TP
.B \-dr
Decompile the current function with one level of recursivity.
.TP
.B \-a \fIquery\fR
Resolve a question using auto mode, which allows the AI to use tools.
.TP
.B \-e \fIk(=v)\fR
Set configuration option (same as 'r2ai.' prefix).
.TP
.B \-h
Show help message.
.TP
.B \-i \fIfile\fR \fIquery\fR
Read a file and ask the LLM with the given query.
.TP
.B \-m
Show selected model, list suggested ones, and choose one.
.TP
.B \-p \fIprovider\fR
Set LLM provider (openai, anthropic, gemini, etc.).
.TP
.B \-q
List available query prompts.
.TP
.B \-q \fIname\fR [\fIinstructions\fR]
Run predefined prompt with optional custom instructions.
.TP
.B \-r
Enter the chat REPL.
.TP
.B \-L
Show chat logs (use -Lj for JSON). Only for auto mode.
.TP
.B \-L-\fIN\fR
Delete the last N messages from the chat history.
.TP
.B \-R
Reset the chat conversation context.
.TP
.B \-Rq [\fItext\fR]
Refresh and query embeddings (see r2ai.data).
.TP
.B \fIquery\fR
Query the selected model and provider with the given query.
.SH CONFIGURATION
Configuration options can be set with \fBr2ai -e \fIkeyname\fR=\fIvalue\fR.
.TP
.B r2ai.api
Name of the provider (e.g., openai). List possibilities with \fBr2ai -e r2ai.api=?\fR.
.TP
.B r2ai.model
Model name. List possibilities with \fBr2ai -e r2ai.model=?\fR.
.TP
.B r2ai.baseurl
Remote LLM base URL (e.g., http://127.0.0.1:11434).
.TP
.B r2ai.max_tokens
Maximum output tokens or total tokens.
.TP
.B r2ai.temperature
Creativity level (0=not creative, 1=very creative).
.TP
.B r2ai.cmds
R2 command to issue and send output in context to model.
.TP
.B r2ai.lang
Programming language for decompilation (only for \fBr2ai -d\fR).
.TP
.B r2ai.hlang
Language for AI responses.
.TP
.B r2ai.prompt
User prompt for \fBr2ai -d\fR.
.TP
.B r2ai.auto.max_runs
Maximum loops in auto mode (\fBr2ai -a\fR).
.TP
.B r2ai.auto.hide_tool_output
Hide tool output in auto mode.
.TP
.B r2ai.auto.yolo
Allow AI to run commands without approval (dangerous, default false).
.TP
.B r2ai.http.use_files
Use files for arguments if CURL argument too long.
.SH QUERY PROMPTS
Query prompts are predefined templates for common tasks. Available prompts:
.TP
.B autoname
Automatically rename functions and variables.
.TP
.B decompile
Decompile the current function.
.TP
.B devices
Analyze device-related code.
.TP
.B dlopen
Analyze dynamic library loading.
.TP
.B explain
Explain the purpose of the current function.
.TP
.B libs
Analyze library usage.
.TP
.B signature
Improve function signatures.
.TP
.B varnames
Rename variables based on context.
.TP
.B vulns
Find vulnerabilities in the current function.
.SH EXAMPLES
.TP
Decompile current function:
.B r2ai -d
.TP
Ask about current function:
.B r2ai -d "what is the input buffer size?"
.TP
Use auto mode:
.B r2ai -a "find vulnerabilities in this function"
.TP
List prompts:
.B r2ai -q
.TP
Run a prompt:
.B r2ai -q explain
.TP
Set provider:
.B r2ai -p openai
.TP
Enter REPL:
.B r2ai -r
.SH SEE ALSO
.BR radare2 (1),
.BR r2pm (1),
.BR r2agent (1)
.SH AUTHOR
radare2 community
+36 -20
View File
@@ -13,6 +13,7 @@ static RCoreHelpMessage help_msg_r2ai = {
"r2ai", " -dr", "Decompile current function (+ 1 level of recursivity)",
"r2ai", " -a [query]", "Resolve question using auto mode",
"r2ai", " -e (k(=v))", "Same as '-e r2ai.'",
"r2ai", " -E", "Edit the r2ai rc file",
"r2ai", " -h", "Show this help message",
"r2ai", " -i [file] [query]", "read file and ask the llm with the given query",
"r2ai", " -m", "show selected model, list suggested ones, choose one",
@@ -44,7 +45,7 @@ R_API char *r2ai(RCorePluginSession *cps, R2AIArgs args) {
R2AI_Messages *msgs = r2ai_msgs_new ();
if (args.input) {
R2AI_Message msg = { .role = "user", .content = args.input };
R2AI_Message msg = { .role = "user", .content = (char *)args.input };
r2ai_msgs_add (msgs, &msg);
}
@@ -134,7 +135,7 @@ static void cmd_r2ai_d(RCorePluginSession *cps, const char *input, const bool re
R_LOG_ERROR (error);
free (error);
} else {
R2_PRINTF ("%s\n", res);
r_cons_printf (core->cons, "%s\n", res);
}
free (res);
r_list_free (cmdslist);
@@ -151,9 +152,9 @@ static void cmd_r2ai_repl(RCorePluginSession *cps) {
}
if (*ptr == '/') {
if (ptr[1] == '?' || r_str_startswith (ptr, "/help")) {
R2_PRINTLN ("/help show this help");
R2_PRINTLN ("/reset reset conversation");
R2_PRINTLN ("/quit same as ^D, leave the repl");
r_cons_println (core->cons, "/help show this help");
r_cons_println (core->cons, "/reset reset conversation");
r_cons_println (core->cons, "/quit same as ^D, leave the repl");
} else if (r_str_startswith (ptr, "/reset")) {
r_strbuf_free (sb);
sb = r_strbuf_new ("");
@@ -172,8 +173,8 @@ static void cmd_r2ai_repl(RCorePluginSession *cps) {
free (error);
} else if (res) {
r_strbuf_appendf (sb, "Assistant: %s\n", res);
R2_PRINTF ("%s\n", res);
R2_FLUSH ();
r_cons_printf (core->cons, "%s\n", res);
r_cons_flush (core->cons);
}
free (res);
}
@@ -239,7 +240,7 @@ static void cmd_r2ai_i(RCorePluginSession *cps, const char *arg) {
R_LOG_ERROR (error);
free (error);
} else {
R2_PRINTF ("%s\n", res);
r_cons_printf (core->cons, "%s\n", res);
}
free (fname);
free (res);
@@ -249,7 +250,7 @@ static void cmd_r2ai_i(RCorePluginSession *cps, const char *arg) {
static void cmd_r2ai_m(RCorePluginSession *cps, const char *input) {
RCore *core = cps->core;
if (R_STR_ISEMPTY (input)) {
R2_PRINTF ("%s\n", r_config_get (core->config, "r2ai.model"));
r_cons_printf (core->cons, "%s\n", r_config_get (core->config, "r2ai.model"));
} else {
r_config_set (core->config, "r2ai.model", input);
}
@@ -295,6 +296,10 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
R2AI_State *state = cps->data;
if (*input == '?' || r_str_startswith (input, "-h")) {
r_core_cmd_help (core, help_msg_r2ai);
} else if (r_str_startswith (input, "-E")) {
char *rc_path = r_file_home (".config/r2ai/rc");
r_cons_editor (core->cons, rc_path, NULL);
free (rc_path);
} else if (r_str_startswith (input, "-e")) {
const char *arg = r_str_trim_head_ro (input + 2);
if (r_str_startswith (arg, "r2ai")) {
@@ -309,10 +314,10 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
const int N = atoi (arg);
R2AI_Messages *messages = r2ai_conversation_get (state);
if (!messages || r_list_length (messages->messages) == 0) {
R2_PRINTF ("No conversation history available\n");
r_cons_printf (core->cons, "No conversation history available\n");
} else {
r2ai_delete_last_messages (messages, N);
R2_PRINTF ("Deleted %d message%s from chat history\n", N > 0? N: 1,
r_cons_printf (core->cons, "Deleted %d message%s from chat history\n", N > 0? N: 1,
(N > 0 && N != 1)? "s": "");
}
} else if (r_str_startswith (input, "-L")) {
@@ -335,7 +340,7 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
for (i = 0; i < rs->size; i++) {
RVdbResult *r = &rs->results[i];
KDNode *n = r->node;
R2_PRINTF ("- (%.4f) %s\n", r->dist_sq, n->text);
r_cons_printf (core->cons, "- (%.4f) %s\n", r->dist_sq, n->text);
}
r_vdb_result_free (rs);
}
@@ -346,10 +351,10 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
} else if (r_str_startswith (input, "-R")) {
R2AI_Messages *messages = r2ai_conversation_get (state);
if (!messages || r_list_length (messages->messages) == 0) {
R2_PRINTF ("No conversation history to reset\n");
r_cons_printf (core->cons, "No conversation history to reset\n");
} else {
r2ai_msgs_clear (messages);
R2_PRINTF ("Chat conversation context has been reset\n");
r_cons_printf (core->cons, "Chat conversation context has been reset\n");
}
} else if (r_str_startswith (input, "-Rq")) {
cmd_r2ai_R (cps, r_str_trim_head_ro (input + 3));
@@ -357,8 +362,11 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
cmd_r2ai_m (cps, r_str_trim_head_ro (input + 2));
} else if (r_str_startswith (input, "-p")) {
const char *provider = r_str_trim_head_ro (input + 2);
r_config_set (core->config, "r2ai.api", provider);
R2_PRINTF ("Provider set to %s\n", provider);
if (R_STR_ISEMPTY (provider)) {
r_cons_printf (core->cons, "%s\n", r_config_get (core->config, "r2ai.api"));
} else {
r_config_set (core->config, "r2ai.api", provider);
}
} else if (r_str_startswith (input, "-q")) {
r2ai_cmd_q (cps, r_str_trim_head_ro (input + 2));
} else if (r_str_startswith (input, "-")) {
@@ -372,7 +380,7 @@ static void cmd_r2ai(RCorePluginSession *cps, const char *input) {
R_FREE (err);
}
if (res) {
R2_PRINTF ("%s\n", res);
r_cons_printf (core->cons, "%s\n", res);
free (res);
}
}
@@ -405,7 +413,7 @@ static bool cb_r2ai_model(void *user, void *data) {
RListIter *iter;
char *model;
r_list_foreach (models, iter, model) {
R2_PRINTLN (model);
r_cons_println (core->cons, model);
}
r_list_free (models);
} else {
@@ -426,7 +434,7 @@ static bool cb_r2ai_model(void *user, void *data) {
if (s) {
*s = 0;
}
R2_PRINTLN (item);
r_cons_println (core->cons, item);
}
r_list_free (items);
free (s);
@@ -452,6 +460,12 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
// Initialize conversation container
r2ai_conversation_init (state);
char *rc_path = r_file_home (".config/r2ai/rc");
if (rc_path && r_file_exists (rc_path)) {
r_core_cmdf (core, ". %s", rc_path);
}
free (rc_path);
r_config_lock (core->config, false);
r_config_set_cb (core->config, "r2ai.api", R2AI_DEFAULT_PROVIDER, &cb_r2ai_api);
{
@@ -505,7 +519,7 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
r_config_desc (core->config, "r2ai.auto.max_runs", "Maximum number of automated steps/runs in auto mode");
r_config_set_b (core->config, "r2ai.auto.hide_tool_output", false);
r_config_desc (core->config, "r2ai.auto.hide_tool_output", "Hide tool output when running automated actions");
r_config_set (core->config, "r2ai.auto.init_commands", "aaa;iI;afl");
r_config_set (core->config, "r2ai.auto.init_commands", "iI"); // "aaa;iI;afl~[3]"
r_config_desc (core->config, "r2ai.auto.init_commands", "Initial commands executed when auto mode starts (semicolon separated)");
r_config_set_b (core->config, "r2ai.auto.yolo", false);
r_config_desc (core->config, "r2ai.auto.yolo", "Execute potentially dangerous commands in auto mode without asking");
@@ -524,6 +538,8 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
r_config_desc (core->config, "r2ai.http.backend", "HTTP backend to use (auto, libcurl, socket, system)");
r_config_set_b (core->config, "r2ai.http.use_files", false);
r_config_desc (core->config, "r2ai.http.use_files", "Use temporary files to pass HTTP request/response payloads (true/false)");
r_config_set_b (core->config, "r2ai.auto.raw", false);
r_config_desc (core->config, "r2ai.auto.raw", "Use prompt engineering for tool calling instead of native API support (true/false)");
r_config_lock (core->config, true);
return true;
}
+10 -14
View File
@@ -11,13 +11,15 @@
#include "r_vdb.h"
#include "markdown.h"
#define R2AI_VERSION "1.2.0"
#define R2AI_VERSION "1.2.2"
#if 0
#define R2AI_DEFAULT_MODEL "gpt-5-mini"
#define R2AI_DEFAULT_PROVIDER "openai"
#else
#define R2AI_DEFAULT_MODEL "gpt-oss:20b"
// #define R2AI_DEFAULT_MODEL "gpt-oss:20b"
// #define R2AI_DEFAULT_MODEL "gemma3:12b"
#define R2AI_DEFAULT_MODEL "mannix/jan-nano"
#define R2AI_DEFAULT_PROVIDER "ollama"
#endif
@@ -27,12 +29,7 @@
#error Your radare2 is too old
#endif
// TODO: Deprecate those defines
#define R2_PRINTF(...) r_cons_printf (core->cons, __VA_ARGS__)
#define R2_FLUSH() r_cons_flush (core->cons)
#define R2_NEWLINE() r_cons_newline (core->cons)
#define R2_PRINTLN(x) r_cons_println (core->cons, x)
#define R2_INTERRUPTED() r_cons_is_breaked (core->cons)
// Tool definition structure
typedef struct {
@@ -71,7 +68,7 @@ typedef struct {
typedef struct {
char *role;
const char *content;
char *content;
char *reasoning_content;
R2AI_ContentBlocks *content_blocks;
char *tool_call_id;
@@ -88,6 +85,8 @@ typedef struct {
char *requires;
char *if_empty;
char *if_command;
char *model;
char *provider;
} R2AIPrompt;
typedef struct {
@@ -317,12 +316,9 @@ R_API bool r2ai_fini(RCorePluginSession *cps);
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCorePluginSession *cps, R2AIArgs args);
R_IPI void cmd_r2ai_logs(RCorePluginSession *cps);
R2AI_ChatResponse *r2ai_rawtools_llmcall(RCorePluginSession *cps, R2AIArgs args);
/**
* Create a conversation with system prompt and optional user message
*/
R_API R2AI_Messages *create_conversation(const char *user_message);
R_IPI void cmd_r2ai_logs(RCorePluginSession *cps);
/**
* Process messages through LLM and handle tool calls recursively
+26 -41
View File
@@ -85,28 +85,13 @@ static void restore_sigint_handler_local(void *old, int old_is_sigaction) {
signal (SIGINT, (void (*) (int))old);
}
// Exponential backoff sleep with jitter
static void r2ai_sleep_with_backoff(int retry_count, int max_sleep_seconds) {
int base_time_ms = 1000;
int max_sleep_ms = max_sleep_seconds * 1000;
int delay_ms = (1 << retry_count) * base_time_ms;
if (delay_ms > max_sleep_ms) {
delay_ms = max_sleep_ms;
// Simple retry sleep
static void sleep_retry(int retry_count, int max_sleep_seconds) {
int delay_seconds = retry_count + 1; // sleep 1, 2, 3, ... seconds
if (delay_seconds > max_sleep_seconds) {
delay_seconds = max_sleep_seconds;
}
int jitter = delay_ms / 5;
if (jitter > 0) {
srand (time (NULL) + retry_count);
delay_ms += (rand () % (jitter * 2)) - jitter;
}
if (delay_ms <= 0) {
delay_ms = base_time_ms;
} else if (delay_ms > max_sleep_ms) {
delay_ms = max_sleep_ms;
}
struct timespec ts;
ts.tv_sec = delay_ms / 1000;
ts.tv_nsec = (delay_ms % 1000) * 1000000;
nanosleep (&ts, NULL);
r_sys_sleep (delay_seconds);
}
#if HAVE_LIBCURL
@@ -183,7 +168,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
if (!response.data) {
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local (r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
@@ -197,7 +182,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
free (response.data);
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local (r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
@@ -260,7 +245,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -276,7 +261,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after rate limiting...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -411,7 +396,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
R_LOG_ERROR ("Failed to create temporary file for curl data");
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break;
@@ -424,7 +409,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
free (temp_file);
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break;
@@ -499,7 +484,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
*code = 0;
@@ -573,7 +558,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
*code = 0;
@@ -627,7 +612,7 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
} else if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
} else {
*code = 0;
@@ -691,7 +676,7 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
*code = 0;
@@ -762,7 +747,7 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after error...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -774,7 +759,7 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -824,7 +809,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
if (!response.data) {
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local (r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
@@ -838,7 +823,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
free (response.data);
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local (r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
@@ -903,7 +888,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -919,7 +904,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after rate limiting...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -1003,7 +988,7 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after error...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -1015,7 +1000,7 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
continue;
}
break; // Exit the retry loop after max retries
@@ -1077,7 +1062,7 @@ R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[],
} else if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
} else {
*code = 0;
}
@@ -1132,7 +1117,7 @@ R_API char *r2ai_http_get(RCore *core, const char *url, const char *headers[], i
} else if (retry_count < max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
r2ai_sleep_with_backoff (retry_count, max_backoff);
sleep_retry (retry_count, max_backoff);
} else {
*code = 0;
}
+297
View File
@@ -0,0 +1,297 @@
/* r2ai - Copyright 2023-2025 pancake */
#define R_LOG_ORIGIN "rawtools"
#include "r2ai.h"
#include "r2ai_priv.h"
// Function to build the rawtools prompt with available tools
static const char *RAWTOOLS_PROMPT =
"Common radare2 commands you can use with r2cmd:\n"
"- i: Show binary information\n"
"- iI: Show detailed binary info\n"
"- iz: Show strings\n"
"- afl: List functions\n"
"- pdf @ function: Disassemble function\n"
"- px @ address: Show hexdump\n"
"- aaa: Analyze all\n"
"- s address: Seek to address\n"
"- ? command: Get help\n\n"
"To run radare2 commands, use the r2cmd tool with JSON arguments.\n\n"
"TOOL: r2cmd\n"
"USAGE: TOOL: r2cmd {\"command\": \"your_command_here\"}\n\n"
"Example: TOOL: r2cmd {\"command\": \"i\"}\n\n"
"If you don't need to run commands, just answer directly.\n\n";
// Function to parse raw tool call from response text
static bool parse_raw_tool_call(const char *response, char **tool_name, char **tool_args) {
if (!response || !tool_name || !tool_args) {
return false;
}
*tool_name = NULL;
*tool_args = NULL;
// First try to parse "TOOL: " format
const char *tool_prefix = "TOOL: ";
const char *line_start = response;
while ((line_start = strstr (line_start, tool_prefix))) {
// Check if it's at the beginning of a line
if (line_start == response || *(line_start - 1) == '\n') {
// Found a tool call
const char *tool_start = line_start + strlen (tool_prefix);
const char *space_pos = strchr (tool_start, ' ');
if (space_pos) {
// Extract tool name
size_t name_len = space_pos - tool_start;
*tool_name = r_str_ndup (tool_start, name_len);
// Extract arguments (rest of the line)
*tool_args = strdup (space_pos + 1);
// Trim whitespace from arguments (simple implementation)
if (*tool_args) {
char *start = *tool_args;
while (*start && (*start == ' ' || *start == '\t')) {
start++;
}
char *end = start + strlen (start) - 1;
while (end > start && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) {
end--;
}
*(end + 1) = '\0';
if (start != *tool_args) {
memmove (*tool_args, start, strlen (start) + 1);
}
}
return true;
}
}
line_start += strlen (tool_prefix);
}
// If no TOOL: format found, try to parse JSON response
// Look for JSON that looks like tool arguments
const char *json_start = strstr (response, "{\"command\"");
if (json_start) {
*tool_name = strdup ("r2cmd");
*tool_args = strdup (json_start);
// Trim trailing whitespace and newlines
char *end = *tool_args + strlen (*tool_args) - 1;
while (end > *tool_args && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) {
*end = '\0';
end--;
}
return true;
}
return false;
}
// Function to handle rawtools mode in LLM call
R2AI_ChatResponse *r2ai_rawtools_llmcall(RCorePluginSession *cps, R2AIArgs args) {
if (!cps) {
return NULL;
}
RCore *core = cps->core;
// Modify the system prompt to include rawtools instructions only if no tool messages
const char *original_system_prompt = args.system_prompt;
// For rawtools, use a shorter system prompt to avoid token limits
const char *short_system_prompt = "You are a reverse engineer using radare2. The binary is loaded. Use r2cmd tool for analysis.";
// Check for initial command output in messages
char *init_output = NULL;
if (args.messages && args.messages->messages) {
RList *msgs = args.messages->messages;
int n_msgs = r_list_length (msgs);
for (int i = 0; i < n_msgs; i++) {
R2AI_Message *msg = r_list_get_n (msgs, i);
if (msg && msg->role && !strcmp (msg->role, "system") && msg->content &&
strstr (msg->content, "Here is some information about the binary")) {
init_output = strdup (msg->content);
break;
}
}
}
char *enhanced_system_prompt = NULL;
if (init_output) {
enhanced_system_prompt = r_str_newf ("%s\n\n%s\n\n%s", short_system_prompt, init_output, RAWTOOLS_PROMPT);
} else {
enhanced_system_prompt = r_str_newf ("%s\n\n%s", short_system_prompt, RAWTOOLS_PROMPT);
}
free (init_output);
// Temporarily modify args to use enhanced prompt and no tools (since we're using prompt engineering)
R2AIArgs rawtools_args = args;
rawtools_args.system_prompt = enhanced_system_prompt;
rawtools_args.tools = NULL; // Disable native tool calling
// Make the LLM call directly to provider
const char *provider = rawtools_args.provider? rawtools_args.provider: r_config_get (core->config, "r2ai.api");
if (!provider) {
R_LOG_ERROR ("No provider defined");
return NULL;
}
R2AI_ChatResponse *response = NULL;
const R2AIProvider *p = r2ai_get_provider (provider);
if (p && p->uses_anthropic_header) {
response = r2ai_anthropic (cps, rawtools_args);
} else {
response = r2ai_openai (cps, rawtools_args);
}
free (enhanced_system_prompt);
if (!response || !response->message || !response->message->content) {
return response;
}
R_LOG_DEBUG ("[RAWTOOLS] Raw model response: %s", response->message->content);
// Check if the response contains a raw tool call
char *tool_name = NULL;
char *tool_args = NULL;
if (parse_raw_tool_call (response->message->content, &tool_name, &tool_args)) {
R_LOG_DEBUG ("Raw tool call detected: %s with args: %s", tool_name, tool_args);
// Find the tool in our tools list
const R2AI_Tools *tools = r2ai_get_tools ();
bool tool_found = false;
for (int i = 0; i < tools->n_tools; i++) {
if (tools->tools[i].name && !strcmp (tools->tools[i].name, tool_name)) {
tool_found = true;
break;
}
}
if (tool_found) {
// Modify the existing response to include tool call information
R2AI_Message *modified_message = R_NEW0 (R2AI_Message);
if (modified_message) {
modified_message->role = strdup ("assistant");
// Remove the TOOL line from content, or set to empty if that's all there is
char *content = strdup (response->message->content? response->message->content: "");
char *tool_line = strstr (content, "TOOL: ");
if (tool_line) {
// Find the end of the line
char *line_end = strchr (tool_line, '\n');
if (line_end) {
// Remove the TOOL line
memmove (tool_line, line_end + 1, strlen (line_end + 1) + 1);
} else {
// The TOOL line is the entire content
*tool_line = '\0';
}
}
modified_message->content = content;
// Set up proper tool call structure
modified_message->tool_calls = R_NEWS0 (R2AI_ToolCall, 1);
if (modified_message->tool_calls) {
modified_message->tool_calls[0].name = tool_name;
modified_message->tool_calls[0].arguments = tool_args;
char id_buf[32];
snprintf (id_buf, sizeof (id_buf), "rawtool_%d", (int)time (NULL));
modified_message->tool_calls[0].id = strdup (id_buf);
modified_message->n_tool_calls = 1;
}
tool_name = NULL;
tool_args = NULL;
// Replace the response message
if (response->message) {
r2ai_message_free ((R2AI_Message *)response->message);
}
*(R2AI_Message **)&response->message = modified_message;
}
} else {
// Unknown tool, modify content to indicate error
R2AI_Message *modified_message = R_NEW0 (R2AI_Message);
if (modified_message) {
modified_message->role = strdup ("assistant");
size_t result_len = strlen ("Unknown tool: ") + strlen (tool_name) + 1;
modified_message->content = malloc (result_len);
if (modified_message->content) {
snprintf (modified_message->content, result_len, "Unknown tool: %s", tool_name);
}
// Replace the response message
if (response->message) {
r2ai_message_free ((R2AI_Message *)response->message);
}
*(R2AI_Message **)&response->message = modified_message;
}
free (tool_name);
free (tool_args);
tool_name = NULL;
tool_args = NULL;
}
return response;
}
// No tool call found
free (tool_name);
free (tool_args);
// If the response has no content, try again with normal mode (without rawtools prompt)
if (!response->message || !response->message->content || !*response->message->content) {
R_LOG_DEBUG ("No tool call found and no content, falling back to normal mode");
// Free the current response
if (response->message) {
r2ai_message_free ((R2AI_Message *)response->message);
}
free (response);
// Modify args to use original system prompt without rawtools enhancement
R2AIArgs fallback_args = args;
fallback_args.system_prompt = original_system_prompt;
fallback_args.tools = args.tools; // Keep tools for fallback
// Call provider directly
const char *provider = fallback_args.provider? fallback_args.provider: r_config_get (core->config, "r2ai.api");
if (!provider) {
R_LOG_ERROR ("No provider defined");
return NULL;
}
R2AI_ChatResponse *fallback_response = NULL;
const R2AIProvider *p = r2ai_get_provider (provider);
if (p && p->uses_anthropic_header) {
fallback_response = r2ai_anthropic (cps, fallback_args);
} else {
fallback_response = r2ai_openai (cps, fallback_args);
}
if (fallback_response && fallback_response->message && fallback_response->message->content && *fallback_response->message->content) {
return fallback_response;
}
// If still no content, create a response with warning
if (fallback_response) {
if (fallback_response->message) {
r2ai_message_free ((R2AI_Message *)fallback_response->message);
}
free (fallback_response);
}
// Create a warning response
R2AI_ChatResponse *warning_response = R_NEW0 (R2AI_ChatResponse);
R2AI_Message *msg = R_NEW0 (R2AI_Message);
msg->role = strdup ("assistant");
msg->content = strdup ("Warning: LLM provided no response content");
*((R2AI_Message **)&warning_response->message) = msg;
R_LOG_WARN ("LLM provided no response content");
return warning_response;
}
return response;
}
+11 -45
View File
@@ -284,31 +284,19 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **e
if (is_multiline) {
// Use editor for multi-line commands
*edited_command = strdup (command);
#if R2_VERSION_NUMBER >= 50909
r_cons_editor (core->cons, NULL, *edited_command);
#else
r_cons_editor (NULL, *edited_command);
#endif
command = *edited_command;
} else {
// For single-line commands, push the command to input buffer
R2_NEWLINE ();
R2_PRINTF ("\x1b[31m[edit cmd]>\x1b[0m ");
R2_FLUSH ();
r_cons_newline (core->cons);
// Push the command to the input buffer
// Get user input with command pre-filled
#if R2_VERSION_NUMBER >= 50909
r_cons_readpush (core->cons, command, strlen (command));
r_cons_readpush (core->cons, "\x05", 1); // Ctrl+E - move to end
const char *readline_result = r_line_readline (core->cons);
#else
r_cons_readpush (command, strlen (command));
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
const char *readline_result = r_line_readline ();
#endif
// Check if interrupted or ESC pressed (readline_result is NULL or empty)
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
if (r_cons_is_breaked (core->cons) || R_STR_ISEMPTY (readline_result)) {
R_LOG_INFO ("Command execution cancelled %s", readline_result);
return strdup ("R2AI_SIGINT");
}
@@ -330,8 +318,8 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **e
if (!hide_tool_output) {
char *red_command = r_str_newf ("\x1b[31m%s\x1b[0m\n", *edited_command);
R2_PRINTF ("%s", red_command);
R2_FLUSH ();
r_cons_printf (core->cons, "%s", red_command);
r_cons_flush (core->cons);
free (red_command);
}
@@ -377,29 +365,17 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
if (is_multiline) {
// Use editor for multi-line scripts
edited_script = strdup (script);
#if R2_VERSION_NUMBER >= 50909
r_cons_editor (core->cons, NULL, edited_script);
#else
r_cons_editor (NULL, edited_script);
#endif
script = edited_script;
} else {
// For single-line scripts, push the script to input buffer
R2_PRINTF ("\x1b[31m[edit js]>\x1b[0m ");
R2_FLUSH ();
#if R2_VERSION_NUMBER >= 50909
r_cons_readpush (core->cons, script, strlen (script));
r_cons_readpush (core->cons, "\x05", 1); // Ctrl+E - move to end
const char *readline_result = r_line_readline (core->cons);
#else
r_cons_readpush (script, strlen (script));
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
const char *readline_result = r_line_readline ();
#endif
// Check if interrupted or ESC pressed (readline_result is NULL or empty)
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
if (r_cons_is_breaked (core->cons) || R_STR_ISEMPTY (readline_result)) {
free (edited_script); // Free if already allocated
return strdup ("R2AI_SIGINT");
}
@@ -419,8 +395,8 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
if (!hide_tool_output) {
char *print_script = r_str_newf ("\n```js\n%s\n```", script);
char *print_script_rendered = r2ai_markdown (print_script);
R2_PRINTF ("%s\n", print_script_rendered);
R2_FLUSH ();
r_cons_printf (core->cons, "%s\n", print_script_rendered);
r_cons_flush (core->cons);
free (print_script);
free (print_script_rendered);
}
@@ -435,11 +411,7 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
return strdup ("{ \"res\":\"Failed to create script payload\" }");
}
#if R2_VERSION_NUMBER >= 50909
char *payload_base64 = r_base64_encode_dyn ((const ut8 *)payload, strlen (payload));
#else
char *payload_base64 = r_base64_encode_dyn ((const char *)payload, strlen (payload));
#endif
if (!payload_base64) {
free (payload);
return strdup ("{ \"res\":\"Failed to encode script\" }");
@@ -481,12 +453,6 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, c
}
bool hide_tool_output = r_config_get_b (core->config, "r2ai.auto.hide_tool_output");
char *print_name = r_str_newf ("\x1b[1;32m\x1b[4m[%s]>\x1b[0m ", tool_name);
R2_PRINTF ("%s\n", print_name);
R2_FLUSH ();
free (print_name);
char *tool_result = NULL;
if (strcmp (tool_name, "r2cmd") == 0) {
@@ -627,10 +593,10 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, c
return r_str_newf ("Error running tool: Unknown error\nCommand: %s", args);
}
if (!hide_tool_output) {
R2_NEWLINE ();
R2_PRINTF ("%s\n", result);
R2_NEWLINE ();
R2_FLUSH ();
r_cons_newline (core->cons);
r_cons_printf (core->cons, "%s\n", result);
r_cons_newline (core->cons);
r_cons_flush (core->cons);
}
r_str_ansi_strip (result);