mirror of
https://github.com/radareorg/r2ai.git
synced 2026-11-10 04:01:27 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8886365925 | ||
|
|
01d8e29420 | ||
|
|
e8b556ee5d | ||
|
|
6c336f48f7 | ||
|
|
53d61831b2 | ||
|
|
cbcff6b9dd | ||
|
|
f04bf8162e | ||
|
|
0206ceb390 | ||
|
|
ff1f788c48 | ||
|
|
df135b2c70 | ||
|
|
c152777bbe | ||
|
|
3e084ce797 |
No files matched your search
@@ -1 +0,0 @@
|
||||
recursive-include r2ai/ui *.tcss
|
||||
+4
-3
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
@@ -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,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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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',
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user