Compare commits

...
1 Commits
Author SHA1 Message Date
pancake db872d4977 r2clippy assistant mode 2026-03-29 16:53:24 +02:00
5 changed files with 89 additions and 8 deletions

No files matched your search

+2
View File
@@ -20,6 +20,7 @@ make user-install
* Auto mode and function calling with any model
* Fix and improve function signatures
* Assistant mode with a REPL chat
* `r2clippy` interactive assistant mode for radare2-focused help
## Experimental
@@ -43,6 +44,7 @@ Usage: r2ai [-args] [...]
| r2ai -q list available query prompts
| r2ai -q [name] (inst) run predefined prompt with optional instructions
| r2ai -r enter the chat repl
| r2ai -r2 enter the interactive r2clippy assistant
| r2ai -L show chat logs (See -Lj for json). Only for auto mode.
| r2ai -L-[N] delete the last (or N last messages from the chat history)
| r2ai -R reset the chat conversation context
+14 -3
View File
@@ -19,7 +19,8 @@ static void show_help() {
" -i <script> Load and interpret script file before executing commands\n"
" -e <var=value> Set configuration variable\n"
" -E Edit the r2ai rc file\n"
" -K Edit the API keys file\n");
" -K Edit the API keys file\n"
" With no prompt, enter the interactive r2clippy assistant\n");
}
static void show_version() {
@@ -38,7 +39,9 @@ static char *build_conversation(RList *conversation) {
static void r2ai_repl(RCorePluginSession *cps, const char *provider, const char *model, RList *conversation) {
RCore *core = cps->core;
r_line_set_prompt (core->cons->line, "[r2ai]> ");
r_line_set_prompt (core->cons->line, r_config_get_b (core->config, "r2ai.clippy")
? "[r2clippy]> "
: "[r2ai]> ");
while (true) {
const char *input = r_line_readline (core->cons);
if (r_cons_is_breaked (core->cons) || R_STR_ISEMPTY (input)) {
@@ -80,8 +83,8 @@ static void r2ai_repl(RCorePluginSession *cps, const char *provider, const char
free (cmd);
} else {
r_cons_println (core->cons, res);
r_list_append (conversation, r_str_newf ("Assistant: %s", res));
}
r_list_append (conversation, r_str_newf ("Assistant: %s", res));
free (res);
}
if (err) {
@@ -94,6 +97,13 @@ static void r2ai_repl(RCorePluginSession *cps, const char *provider, const char
}
}
static void use_default_clippy_role(RCore *core) {
const char *system_prompt = r_config_get (core->config, "r2ai.system");
if (system_prompt && !strcmp (system_prompt, R2AI_DEFAULT_SYSTEM_PROMPT)) {
r2ai_load_role (core, "r2clippy");
}
}
int main(int argc, const char **argv) {
int c;
const char *provider = NULL;
@@ -222,6 +232,7 @@ int main(int argc, const char **argv) {
}
} else {
if (opt.ind >= argc) {
use_default_clippy_role (core);
r2ai_repl (&cps, provider, model, conversation);
} else {
const char *prompt = argv[opt.ind];
+68 -5
View File
@@ -23,6 +23,7 @@ static RCoreHelpMessage help_msg_r2ai = {
"r2ai", " -q", "list available query prompts",
"r2ai", " -q [name] (inst)", "run predefined prompt with optional instructions",
"r2ai", " -r", "enter the chat repl",
"r2ai", " -r2", "enter the r2clippy interactive assistant",
"r2ai", " -L", "show chat logs (See -Lj for json). Only for auto mode.",
"r2ai", " -C", "compact conversation history",
"r2ai", " -LR", "create a log report",
@@ -135,7 +136,9 @@ static void cmd_r2ai_repl(RCorePluginSession *cps) {
RCore *core = cps->core;
RStrBuf *sb = r_strbuf_new ("");
while (true) {
r_line_set_prompt (core->cons->line, "[r2ai]> ");
r_line_set_prompt (core->cons->line, r_config_get_b (core->config, "r2ai.clippy")
? "[r2clippy]> "
: "[r2ai]> ");
const char *ptr = r_line_readline (core->cons);
if (R_STR_ISEMPTY (ptr)) {
break;
@@ -163,7 +166,13 @@ static void cmd_r2ai_repl(RCorePluginSession *cps) {
free (error);
} else if (res) {
r_strbuf_appendf (sb, "Assistant: %s\n", res);
r_cons_printf (core->cons, "%s\n", res);
if (r_config_get_b (core->config, "r2ai.clippy")) {
char *cmd = r_str_newf ("?E %s", res);
r_core_cmd_call (core, cmd);
free (cmd);
} else {
r_cons_printf (core->cons, "%s\n", res);
}
r_cons_flush (core->cons);
}
free (res);
@@ -312,6 +321,58 @@ static bool load_r2airc(RCorePluginSession *cps) {
return true;
}
static char *resolve_role_path(const char *name) {
if (R_STR_ISEMPTY (name)) {
return NULL;
}
if (r_file_exists (name)) {
return r_file_abspath (name);
}
if (strchr (name, '/') || strchr (name, '\\')) {
return NULL;
}
const bool has_ext = r_str_endswith (name, ".txt");
const char *suffix = has_ext? "": ".txt";
const char *candidates[] = {
"~/.config/r2ai/roles/%s%s",
"~/.config/r2ai/role/%s%s",
"doc/role/%s%s",
"../doc/role/%s%s",
NULL
};
int i;
for (i = 0; candidates[i]; i++) {
char *candidate = r_str_newf (candidates[i], name, suffix);
char *path = r_file_abspath (candidate);
free (candidate);
if (path && r_file_exists (path)) {
return path;
}
free (path);
}
return NULL;
}
R_API bool r2ai_load_role(RCore *core, const char *name) {
R_RETURN_VAL_IF_FAIL (core && name, false);
char *path = resolve_role_path (name);
if (!path) {
R_LOG_ERROR ("Cannot find role '%s'", name);
return false;
}
char *content = r_file_slurp (path, NULL);
if (!content) {
R_LOG_ERROR ("Cannot read role file '%s'", path);
free (path);
return false;
}
r_str_trim (content);
r_config_set (core->config, "r2ai.system", content);
free (content);
free (path);
return true;
}
R_API void cmd_r2ai(RCorePluginSession *cps, const char *input) {
RCore *core = cps->core;
R2AI_State *state = cps->data;
@@ -381,6 +442,10 @@ R_API void cmd_r2ai(RCorePluginSession *cps, const char *input) {
}
} else if (r_str_startswith (input, "-i")) {
cmd_r2ai_i (cps, r_str_trim_head_ro (input + 2));
} else if (r_str_startswith (input, "-r2")) {
r_config_set_b (core->config, "r2ai.clippy", true);
r2ai_load_role (core, "r2clippy");
cmd_r2ai_repl (cps);
} else if (r_str_startswith (input, "-r")) {
cmd_r2ai_repl (cps);
} else if (r_str_startswith (input, "-R")) {
@@ -566,9 +631,7 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
r_config_desc (core->config, "r2ai.hlang", "Human language for prompts/messages (e.g. english)");
r_config_set (
core->config, "r2ai.system",
"You are a reverse engineer. The user is reversing a binary, using "
"radare2. The user will ask questions about the binary and you will "
"respond with the answer to the best of your ability.");
R2AI_DEFAULT_SYSTEM_PROMPT);
r_config_set (
core->config, "r2ai.prompt",
"Rewrite this function and respond ONLY with code, replace goto/labels "
+4
View File
@@ -28,6 +28,10 @@
#endif
#define R2AI_DEFAULT_VECTORS 16
#define R2AI_DEFAULT_SYSTEM_PROMPT \
"You are a reverse engineer. The user is reversing a binary, using " \
"radare2. The user will ask questions about the binary and you will " \
"respond with the answer to the best of your ability."
// HTTP configuration structure
typedef struct {
+1
View File
@@ -27,5 +27,6 @@ R_IPI void r2ai_refresh_embeddings(RCorePluginSession *cps);
R_API char *r2ai_apikeys_path(bool *exists);
R_API void r2ai_apikeys_edit(RCorePluginSession *cps);
R_API char *r2ai_apikeys_get(const char *provider);
R_API bool r2ai_load_role(RCore *core, const char *name);
#endif