Compare commits

...
6 Commits
68 changed files with 757 additions and 1250 deletions

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -1 +1 @@
1.2.2
1.2.4
+1 -1
View File
@@ -1,6 +1,6 @@
(function () {
// Constants and configuration
const VERSION = "1.2.2";
const VERSION = "1.2.4";
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";
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Automatically suggest a better name for this function'
version: 0.1
author: pancake
command: afv;r2ai -d
if-empty: exit
---
Suggest a better name for this function. Output only the suggested function name (must be one alphanumeric word) prefixed with `'afn '. Check the code below:
-4
View File
@@ -1,4 +0,0 @@
Title: Automatically suggest a better name for this function
Author: pancake
Command: afv;r2ai -d
Prompt: Suggest a better name for this function. Output only the suggested function name (must be one alphanumeric word) prefixed with `'afn '. Check the code below:
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Augmented decompilation based on LLM'
version: 0.1
author: pancake
command: pdc
if-empty: exit
---
$(-e r2ai.prompt). Translate the code into $(-e r2ai.lang) programming language.
-4
View File
@@ -1,4 +0,0 @@
Title: Augmented decompilation based on LLM
Author: pancake
Command: pdc
Prompt: $(-e r2ai.prompt). Translate the code into $(-e r2ai.lang) programming language.
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Find and explain devices used'
version: 0.1
author: pancake
command: izqq~^/dev
if-empty: exit
---
explain in one line the purpose of each of these devices
-4
View File
@@ -1,4 +0,0 @@
Title: Find and explain devices used
Author: pancake
Command: izqq~^/dev
Prompt: explain in one line the purpose of each of these devices
+8
View File
@@ -0,0 +1,8 @@
---
description: 'List libraries loaded with dlopen'
version: 0.1
author: pancake
command: iiq~dlopen
if-empty: This binary is not using dlopen
---
Some libraries are loaded
-6
View File
@@ -1,6 +0,0 @@
Title: List libraries loaded with dlopen
Description: Some libraries are loaded
Requires: analysis
Command: iiq~dlopen
If-Empty: This binary is not using dlopen
If-Command: e emu.str=true;pd--5@@c:axtq sym.imp.dlopen
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Explain the current function'
version: 0.1
author: pancake
command: r2ai -d
if-empty: exit
---
Analyze function calls, comments and strings, ignore registers and memory accesses. Considering the references and involved loops; explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in $(-e r2ai.hlang)
-4
View File
@@ -1,4 +0,0 @@
Title: Explain the current function
Author: pancake
Command: r2ai -d
Prompt: Analyze function calls, comments and strings, ignore registers and memory accesses. Considering the references and involved loops; explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in $(-e r2ai.hlang)
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Group imports by Libraries'
version: 0.1
author: pancake
command: iiq;il
if-empty: exit
---
Group the imports that belong to each library and describe its purpose and usage scope
-4
View File
@@ -1,4 +0,0 @@
Title: Group imports by Libraries
Author: pancake
Commands: iiq;il
Query: Group the imports that belong to each library and describe its purpose and usage scope
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Suggest an improved function signature'
version: 0.1
author: pancake
command: ?e variables;afv;?e function name:;afd;?e current signature:?e afs
if-empty: exit
---
Analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body. Output must be *ONLY* the function signature prefixed with the `'afs ` text
-4
View File
@@ -1,4 +0,0 @@
Title: Suggest an improved function signature
Author: pancake
Command: ?e variables;afv;?e function name:;afd;?e current signature:?e afs
Prompt: Analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body. Output must be *ONLY* the function signature prefixed with the `'afs ` text
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Better variable names'
version: 0.1
author: pancake
command: afv
if-empty: exit
---
Output only the radare2 command without markdown, guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands
-4
View File
@@ -1,4 +0,0 @@
Title: Better variable names
Author: pancake
Command: afv
Prompt: Output only the radare2 command without markdown, guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands
+8
View File
@@ -0,0 +1,8 @@
---
description: 'Find vulnerabilities or bugs in the current function'
version: 0.1
author: pancake
command: pdc
if-empty: exit
---
Find vulnerabilities, do not show the code, only a short summary of the resulting analysis report including suggested good practices to address the problem and a practical exploit example.
-4
View File
@@ -1,4 +0,0 @@
Title: Find vulnerabilities or bugs in the current function
Author: pancake
Command: pdc
Prompt: Find vulnerabilities, do not show the code, only a short summary of the resulting analysis report including suggested good practices to address the problem and a practical exploit example.
+2 -2
View File
@@ -45,7 +45,7 @@ OBJS+=vdb.o
OBJS+=tools.o
OBJS+=messages.o
OBJS+=anthropic.o
OBJS+=r2ai_http.o
OBJS+=http.o
OBJS+=openai.o
OBJS+=markdown.o
OBJS+=query.o
@@ -66,7 +66,7 @@ doc-install:
vdb.o: vdb_embed.inc.c
fmt format indent:
clang-format-radare2 *.c
clang-format-radare2 *.c http/*.c
user-install: user-uninstall
mkdir -p $(R2USRPLUGDIR)
+232
View File
@@ -0,0 +1,232 @@
#include "r2ai.h"
#include <signal.h>
#include <time.h>
// Global flag for tracking interrupt status
static volatile sig_atomic_t r2ai_http_interrupted = 0;
// Helper function to get HTTP configuration with defaults
static R2AI_HttpConfig get_http_config(RCore *core) {
R2AI_HttpConfig config = { 120, 10, 30 };
if (core) {
int t = r_config_get_i (core->config, "r2ai.http.timeout");
if (t > 0) {
config.timeout = t;
}
int mr = r_config_get_i (core->config, "r2ai.http.max_retries");
if (mr >= 0) {
config.max_retries = mr;
}
int mb = r_config_get_i (core->config, "r2ai.http.max_backoff");
if (mb > 0) {
config.max_backoff = mb;
}
}
return config;
}
#ifndef _WIN32
// Signal handler for timeout (SIGALRM)
static void r2ai_http_sigint_handler(int sig) {
if (sig == SIGALRM) {
r2ai_http_interrupted = 1;
}
}
#endif
// Portable break callback for r_cons_break (handles SIGINT)
static void r2ai_http_break_callback(void *user) {
(void)user;
r2ai_http_interrupted = 1;
}
// Function pointer type for HTTP request implementations
typedef HttpResponse(*HttpRequestFunc)(const HTTPRequest *request);
// 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;
}
r_sys_sleep (delay_seconds);
}
// Generic HTTP request with retry logic
static HttpResponse r2ai_http_request_with_retry(HttpRequestFunc func, const HTTPRequest *request, RCore *core) {
// Install portable break handler for interruption
if (core && core->cons) {
r_cons_break_push (core->cons, r2ai_http_break_callback, NULL);
}
// Reset interrupt flag
r2ai_http_interrupted = 0;
// Retry loop
HttpResponse result = { 0 };
int retry_count = 0;
bool success = false;
while (!success && retry_count <= request->config.max_retries && !r2ai_http_interrupted) {
result = func (request);
// Check if we were interrupted
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
if (result.body) {
free (result.body);
}
result.body = NULL;
result.code = -1;
break; // Exit the retry loop
}
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
if (result.code > 0) {
if (result.code == 429 || (result.code >= 500 && result.code < 600)) {
R_LOG_WARN ("Server returned %d response code", result.code);
free (result.body);
result.body = NULL;
result.code = 0;
if (retry_count < request->config.max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after error...", retry_count, request->config.max_retries);
sleep_retry (retry_count, request->config.max_backoff);
continue;
}
result.code = -1;
break; // Exit the retry loop after max retries
}
}
// Check for other failures
if (result.code <= 0 || !result.body) {
if (result.body) {
free (result.body);
}
result.body = NULL;
result.code = -1;
if (retry_count < request->config.max_retries) {
retry_count++;
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, request->config.max_retries);
sleep_retry (retry_count, request->config.max_backoff);
continue;
}
break; // Exit the retry loop after max retries
}
success = true;
}
// Restore the original break handler
if (core && core->cons) {
r_cons_break_pop (core->cons);
}
return result;
}
#include "http/libcurl.inc.c"
#include "http/pwsh.inc.c"
#include "http/curl.inc.c"
#include "http/r2.inc.c"
static HttpRequestFunc select_backend(const char *backend, bool is_post) {
HttpRequestFunc func = NULL;
// Select the appropriate backend function
if (!strcmp (backend, "auto")) {
// Auto-select the best available backend
#if defined(_WIN32)
backend = "pwsh";
#elif USE_LIBCURL && HAVE_LIBCURL
backend = "libcurl";
#else
backend = "system";
#endif
R_LOG_DEBUG ("Auto-selected %s backend", backend);
}
if (!strcmp (backend, "system") || !strcmp (backend, "curl")) {
func = is_post? system_curl_post: system_curl_get;
} else if (!strcmp (backend, "libcurl")) {
#if USE_LIBCURL && HAVE_LIBCURL
func = is_post? curl_http_post: curl_http_get;
#else
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
#endif
} else if (!strcmp (backend, "socket")) {
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
} else if (!strcmp (backend, "pwsh") || !strcmp (backend, "powershell")) {
#if defined(_WIN32)
func = is_post? windows_http_post: windows_http_get;
#else
R_LOG_WARN ("powershell is only available on Windows");
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
#endif
} else if (!strcmp (backend, "r2curl")) {
r_sys_setenv ("R2_CURL", "1");
func = is_post? socket_http_post_with_interrupt: socket_http_get_with_interrupt;
}
return func;
}
// Generic HTTP request function that handles backend selection
static HttpResponse r2ai_http_request(const char *method, RCore *core, const char *url, const char *headers[], const char *data) {
(void)method;
bool is_post = (data != NULL);
HTTPRequest request = {
.config = get_http_config (core),
.url = url,
.data = data,
.headers = headers
};
const char *backend = r_config_get (core->config, "r2ai.http.backend");
HttpRequestFunc func = select_backend (backend, is_post);
if (func) {
return r2ai_http_request_with_retry (func, &request, core);
}
R_LOG_ERROR ("Cannot find a valid http backend");
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
}
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen) {
HttpResponse response = r2ai_http_request ("POST", core, url, headers, data);
if (response.code <= 0) {
if (response.body) {
free (response.body);
}
if (code) {
*code = response.code;
}
return NULL;
}
if (code) {
*code = response.code;
}
if (rlen) {
*rlen = response.length;
}
return response.body;
}
R_API char *r2ai_http_get(RCore *core, const char *url, const char *headers[], int *code, int *rlen) {
HttpResponse response = r2ai_http_request ("GET", core, url, headers, NULL);
if (response.code <= 0) {
if (response.body) {
free (response.body);
}
if (code) {
*code = response.code;
}
return NULL;
}
if (code) {
*code = response.code;
}
if (rlen) {
*rlen = response.length;
}
return response.body;
}
+63
View File
@@ -0,0 +1,63 @@
static HttpResponse build_and_execute_curl(const char *cmd_start, const HTTPRequest *request, const char *input_data) {
HttpResponse error = { .code = -1 };
int timeout = request->config.timeout;
if (!request->url) {
return error;
}
// Compose curl command
RStrBuf *cmd = r_strbuf_new (cmd_start);
// Add timeout
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
// Add headers
if (request->headers) {
for (int i = 0; request->headers[i] != NULL; i++) {
r_strbuf_appendf (cmd, " -H '%s'", request->headers[i]);
}
}
r_strbuf_appendf (cmd, " -w '\\n%%{http_code}' '%s'", request->url);
char *cmd_str = r_strbuf_drain (cmd);
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
char *response = r_sys_cmd_str (cmd_str, input_data, NULL);
free (cmd_str);
if (!response) {
return error;
}
// Parse the response: body\ncode
char *last_nl = (char *)r_str_rchr (response, NULL, '\n');
int code = -1;
char *body = response;
if (last_nl) {
*last_nl = '\0';
code = atoi (last_nl + 1);
if (code == 0) {
code = -1; // Invalid code
}
} else {
// No newline, assume 200 if response
code = 200;
}
return (HttpResponse){ .body = body, .code = code, .length = strlen (body) };
}
HttpResponse system_curl_get(const HTTPRequest *request) {
R_LOG_DEBUG ("Using system curl GET");
return build_and_execute_curl ("curl -s", request, NULL);
}
HttpResponse system_curl_post(const HTTPRequest *request) {
HttpResponse error = { .code = -1 };
if (!request->url || !request->data) {
return error;
}
R_LOG_DEBUG ("Using system curl POST without files");
HttpResponse res = build_and_execute_curl ("curl -s -X POST -d @-", request, request->data);
return res;
}
+140
View File
@@ -0,0 +1,140 @@
#if HAVE_LIBCURL
#include <curl/curl.h>
// Struct to store response data
typedef struct {
char *data;
size_t size;
} CurlResponse;
// Callback function for curl to write response data
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
// Check if we've been interrupted
if (r2ai_http_interrupted) {
return 0; // Return 0 to abort the transfer
}
size_t realsize = size * nmemb;
CurlResponse *mem = (CurlResponse *)userp;
char *ptr = realloc (mem->data, mem->size + realsize + 1);
if (!ptr) {
R_LOG_ERROR ("Not enough memory for curl response");
return 0; // Out of memory
}
mem->data = ptr;
memcpy (&(mem->data[mem->size]), contents, realsize);
mem->size += realsize;
mem->data[mem->size] = 0;
return realsize;
}
// Curl progress callback function to handle interrupts
static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
return r2ai_http_interrupted? 1: 0;
}
static HttpResponse curl_http_request(const HTTPRequest *request, bool is_post) {
HttpResponse error = { .code = -1 };
if (!request->url || (is_post && !request->data)) {
return error;
}
CURL *curl;
CURLcode res;
struct curl_slist *curl_headers = NULL;
CurlResponse response = { 0 };
// Initialize response
response.data = malloc (1);
if (!response.data) {
return error;
}
response.data[0] = '\0';
response.size = 0;
curl = curl_easy_init ();
if (!curl) {
free (response.data);
return error;
}
// Set URL
curl_easy_setopt (curl, CURLOPT_URL, request->url);
// Set method-specific options
if (is_post) {
curl_easy_setopt (curl, CURLOPT_POST, 1L);
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, request->data);
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long)strlen (request->data));
} else {
curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
}
// Set headers if provided
if (request->headers) {
for (int i = 0; request->headers[i] != NULL; i++) {
curl_headers = curl_slist_append (curl_headers, request->headers[i]);
}
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, curl_headers);
}
// Set write callback
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *)&response);
// Set progress callback to handle interrupts
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt (curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt (curl, CURLOPT_XFERINFODATA, NULL);
// Set timeout options
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds connect timeout
curl_easy_setopt (curl, CURLOPT_TIMEOUT, (long)request->config.timeout); // Use configured timeout
// Follow redirects
curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
// Perform the request
res = curl_easy_perform (curl);
// Check for interruption
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
free (response.data);
curl_slist_free_all (curl_headers);
curl_easy_cleanup (curl);
return error;
}
// Get response code
long http_code;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
// Check for errors
if (res != CURLE_OK) {
R_LOG_ERROR ("curl_easy_perform () failed: %s", curl_easy_strerror (res));
free (response.data);
curl_slist_free_all (curl_headers);
curl_easy_cleanup (curl);
return error;
}
// If we get here, the request was successful
// Cleanup
curl_slist_free_all (curl_headers);
curl_easy_cleanup (curl);
return (HttpResponse){ .body = response.data, .code = (int)http_code, .length = response.size };
}
HttpResponse curl_http_get(const HTTPRequest *request) {
return curl_http_request (request, false);
}
HttpResponse curl_http_post(const HTTPRequest *request) {
return curl_http_request (request, true);
}
#endif // HAVE_LIBCURL
+77
View File
@@ -0,0 +1,77 @@
#ifdef _WIN32
// Helper to append headers to PowerShell command
static void append_headers_to_cmd(RStrBuf *cmd, const char *const *headers) {
r_strbuf_appendf (cmd, "$ProgressPreference='SilentlyContinue';$headers=@{");
if (headers) {
for (int i = 0; headers[i]; i++) {
char *header = strdup (headers[i]);
char *colon = strchr (header, ':');
if (colon) {
*colon = '\0';
char *key = r_str_trim_dup (header);
char *value = r_str_trim_dup (colon + 1);
r_strbuf_appendf (cmd, "'%s'='%s';", key, value);
free (key);
free (value);
}
free (header);
}
}
r_strbuf_appendf (cmd, "};");
}
// Helper to parse PowerShell response
static HttpResponse parse_powershell_response(char *full_response) {
if (full_response) {
char *newline = strchr (full_response, '\n');
if (newline) {
*newline = '\0';
int code = atoi (full_response);
char *body = newline + 1;
char *body_copy = strdup (body);
size_t length = strlen (body);
free (full_response);
return (HttpResponse){ .body = body_copy, .code = code, .length = length };
} else {
return (HttpResponse){ .body = full_response, .code = 200, .length = strlen (full_response) };
}
}
return (HttpResponse){ .code = -1 };
}
/**
* Windows-specific HTTP POST using PowerShell
*/
HttpResponse windows_http_post(const HTTPRequest *request) {
const char *url = request->url;
const char *const *headers = request->headers;
const char *data = request->data;
int timeout = request->config.timeout;
RStrBuf *cmd = r_strbuf_new ("powershell -Command \"");
append_headers_to_cmd (cmd, headers);
r_strbuf_appendf (cmd, "$body='%s';", data);
r_strbuf_appendf (cmd, "try{$r=Invoke-WebRequest -Method Post -Uri '%s' -Headers $headers -Body $body -TimeoutSec %d;", url, timeout);
r_strbuf_appendf (cmd, "Write-Host $r.StatusCode;$r.Content}catch{Write-Host 0;$_.Exception.Message}\"");
char *cmd_str = r_strbuf_drain (cmd);
char *full_response = r_sys_cmd_str (cmd_str, NULL, NULL);
free (cmd_str);
return parse_powershell_response (full_response);
}
/**
* Windows-specific HTTP GET using PowerShell
*/
HttpResponse windows_http_get(const HTTPRequest *request) {
const char *url = request->url;
const char *const *headers = request->headers;
int timeout = request->config.timeout;
RStrBuf *cmd = r_strbuf_new ("powershell -Command \"");
append_headers_to_cmd (cmd, headers);
r_strbuf_appendf (cmd, "try{$r=Invoke-WebRequest -Method Get -Uri '%s' -Headers $headers -TimeoutSec %d;", url, timeout);
r_strbuf_appendf (cmd, "Write-Host $r.StatusCode;$r.Content}catch{Write-Host 0;$_.Exception.Message}\"");
char *cmd_str = r_strbuf_drain (cmd);
char *full_response = r_sys_cmd_str (cmd_str, NULL, NULL);
free (cmd_str);
return parse_powershell_response (full_response);
}
#endif
+57
View File
@@ -0,0 +1,57 @@
extern volatile sig_atomic_t r2ai_http_interrupted;
extern void r2ai_http_sigint_handler(int sig);
// TODO: r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
// Socket implementation with interrupt handling and retry logic
static HttpResponse socket_http_post_with_interrupt(const HTTPRequest *request) {
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (request->config.timeout); // Use configured timeout
#endif
// Make the request
int code = 0;
int rlen = 0;
char *result = r_socket_http_post (request->url, (const char **)request->headers, request->data, &code, &rlen);
// Clear the alarm
#if R2__UNIX__
alarm (0);
#endif
// Check if we were interrupted
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
free (result);
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
}
return (HttpResponse){ .body = result, .code = code, .length = rlen };
}
// Socket implementation for GET requests
static HttpResponse socket_http_get_with_interrupt(const HTTPRequest *request) {
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (request->config.timeout); // Use configured timeout
#endif
// Make the request - use r_socket_http_get if available
int code = 0;
int rlen = 0;
char *result = r_socket_http_get (request->url, (const char **)request->headers, &code, &rlen);
#if R2__UNIX__
alarm (0);
#endif
// Check if we were interrupted
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
free (result);
return (HttpResponse){ .body = NULL, .code = -1, .length = 0 };
}
return (HttpResponse){ .body = result, .code = code, .length = rlen };
}
+54 -30
View File
@@ -150,7 +150,42 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCorePluginSession *cps, R2AIArgs args) {
return res;
}
/* Return a malloc'd API key read from the environment or from ~/.r2ai.<provider>-key
static char *r2ai_get_api_key_from_config(const char *provider) {
char *config_file = r_file_abspath ("~/.config/r2ai/apikeys.txt");
if (!r_file_exists (config_file)) {
free (config_file);
return NULL;
}
char *content = r_file_slurp (config_file, NULL);
free (config_file);
if (!content) {
return NULL;
}
RList *lines = r_str_split_list (content, "\n", 0);
free (content);
if (!lines) {
return NULL;
}
char *prefix = r_str_newf ("%s=", provider);
char *key = NULL;
RListIter *iter;
char *line;
r_list_foreach (lines, iter, line) {
if (r_str_startswith (line, prefix)) {
char *eq = strchr (line, '=');
if (eq) {
key = strdup (eq + 1);
r_str_trim (key);
}
break;
}
}
r_list_free (lines);
free (prefix);
return key;
}
/* Return a malloc'd API key read from the env, ~/.config/r2ai/apikeys.txt, or ~/.r2ai.<provider>-key
* Caller is responsible for freeing the returned string (or NULL if not found). */
R_IPI char *r2ai_get_api_key(RCore *core, const char *provider) {
(void)core;
@@ -163,16 +198,19 @@ R_IPI char *r2ai_get_api_key(RCore *core, const char *provider) {
api_key = s;
} else {
free (s);
char *api_key_filename = r_str_newf ("~/.r2ai.%s-key", provider);
char *absolute_apikey = r_file_abspath (api_key_filename);
if (r_file_exists (absolute_apikey)) {
api_key = r_file_slurp (absolute_apikey, NULL);
if (api_key) {
r_str_trim (api_key);
api_key = r2ai_get_api_key_from_config (provider);
if (!api_key) {
char *api_key_filename = r_str_newf ("~/.r2ai.%s-key", provider);
char *absolute_apikey = r_file_abspath (api_key_filename);
if (r_file_exists (absolute_apikey)) {
api_key = r_file_slurp (absolute_apikey, NULL);
if (api_key) {
r_str_trim (api_key);
}
}
free (api_key_filename);
free (absolute_apikey);
}
free (api_key_filename);
free (absolute_apikey);
}
return api_key;
}
@@ -190,15 +228,13 @@ R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider) {
if (r_str_startswith (host, "http")) {
if (!strcmp (provider, "openai")) {
return r_str_newf ("%s/v1", host);
} else {
return r_str_newf ("%s/api", host);
}
return r_str_newf ("%s/api", host);
}
if (!strcmp (provider, "openai")) {
return r_str_newf ("http://%s/v1", host);
} else {
return r_str_newf ("http://%s/api", host);
}
return r_str_newf ("http://%s/api", host);
}
}
@@ -211,16 +247,9 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
}
// Create models endpoint URL
char *models_url = NULL;
const R2AIProvider *prov = r2ai_get_provider (provider);
if (prov && prov->uses_tags_endpoint) {
models_url = r_str_newf ("%s/tags", purl);
} else {
models_url = r_str_newf ("%s/models", purl);
}
if (!models_url) {
return NULL;
}
const bool usetags = (prov && prov->uses_tags_endpoint);
char *models_url = r_str_newf ("%s/%s", purl, usetags? "tags": "models");
// Get API key for authentication (except for providers that don't require it)
char *api_key = NULL;
@@ -279,13 +308,9 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
RJson *json = r_json_parse (response);
if (json) {
const RJson *data;
const R2AIProvider *prov = r2ai_get_provider (provider);
if (prov && prov->uses_tags_endpoint) {
data = r_json_get (json, "models");
} else {
data = r_json_get (json, "data");
}
const bool usetags = (prov && prov->uses_tags_endpoint);
const RJson *data = r_json_get (json, usetags? "models": "data");
if (data && data->type == R_JSON_ARRAY) {
const RJson *model_item = data->children.first;
@@ -311,8 +336,7 @@ R_IPI RList *r2ai_fetch_available_models(RCore *core, const char *provider) {
}
R_IPI void r2ai_list_providers(RCore *core, RStrBuf *sb) {
size_t i;
for (i = 0; r2ai_providers[i].name; i++) {
for (size_t i = 0; r2ai_providers[i].name; i++) {
if (sb) {
if (i > 0) {
r_strbuf_append (sb, ", ");
+2 -2
View File
@@ -1,5 +1,5 @@
project('r2ai', 'c',
version : '1.2.2',
version : '1.2.4',
default_options : ['warning_level=2', 'c_std=c11']
)
@@ -194,7 +194,7 @@ srcs = files(
'rawtools.c',
'messages.c',
'anthropic.c',
'r2ai_http.c',
'http.c',
'openai.c',
'markdown.c',
)
+1 -39
View File
@@ -71,45 +71,7 @@ static R2AIPrompt *parse_prompt_file(const char *filepath) {
prompt->prompt = strdup (prompt_start);
}
} 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_LOG_ERROR ("Invalid prompt file format: %s", filepath);
}
free (content);
+32 -2
View File
@@ -451,6 +451,36 @@ static bool cb_r2ai_model(void *user, void *data) {
return true;
}
static bool cb_r2ai_http_backend(void *user, void *data) {
RCore *core = (RCore *)user;
RConfigNode *node = (RConfigNode *)data;
if (*node->value == '?') {
r_cons_println (core->cons, "auto");
r_cons_println (core->cons, "libcurl");
r_cons_println (core->cons, "socket");
r_cons_println (core->cons, "system");
r_cons_println (core->cons, "curl");
r_cons_println (core->cons, "pwsh");
r_cons_println (core->cons, "powershell");
r_cons_println (core->cons, "r2curl");
return false;
}
// Validate the backend
if (!strcmp (node->value, "auto") ||
!strcmp (node->value, "libcurl") ||
!strcmp (node->value, "socket") ||
!strcmp (node->value, "system") ||
!strcmp (node->value, "curl") ||
!strcmp (node->value, "pwsh") ||
!strcmp (node->value, "powershell") ||
!strcmp (node->value, "r2curl")) {
return true;
}
R_LOG_ERROR ("Invalid backend '%s'. Use '?' to list valid backends.", node->value);
return false;
// return false; // always return false to test
}
R_IPI bool r2ai_init(RCorePluginSession *cps) {
RCore *core = cps->core;
// Initialize state
@@ -476,6 +506,8 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
r_config_desc (core->config, "r2ai.api", desc);
free (desc);
}
r_config_set_cb (core->config, "r2ai.http.backend", "auto", &cb_r2ai_http_backend); // Options: auto, libcurl, socket, system, curl, pwsh, powershell, r2curl
r_config_desc (core->config, "r2ai.http.backend", "HTTP backend to use (auto, libcurl, socket, system, curl, pwsh, powershell, r2curl)");
r_config_set_cb (core->config, "r2ai.model", R2AI_DEFAULT_MODEL, &cb_r2ai_model);
r_config_desc (core->config, "r2ai.model", "Model identifier for the selected provider (e.g. gpt-5-mini)");
r_config_set (core->config, "r2ai.baseurl", "");
@@ -534,8 +566,6 @@ R_IPI bool r2ai_init(RCorePluginSession *cps) {
r_config_desc (core->config, "r2ai.http.max_retries", "Maximum number of HTTP retries for failed requests");
r_config_set_i (core->config, "r2ai.http.max_backoff", 30);
r_config_desc (core->config, "r2ai.http.max_backoff", "Maximum backoff time (seconds) between HTTP retries");
r_config_set (core->config, "r2ai.http.backend", "auto"); // Options: auto, libcurl, socket, system
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);
+23 -1
View File
@@ -11,7 +11,7 @@
#include "r_vdb.h"
#include "markdown.h"
#define R2AI_VERSION "1.2.2"
#define R2AI_VERSION "1.2.4"
#if 0
#define R2AI_DEFAULT_MODEL "gpt-5-mini"
@@ -25,6 +25,28 @@
#define R2AI_DEFAULT_VECTORS 16
// HTTP configuration structure
typedef struct {
int timeout;
int max_retries;
int max_backoff;
} R2AI_HttpConfig;
// HTTP request structure
typedef struct {
R2AI_HttpConfig config;
const char *url;
const char *data;
const char * const *headers;
} HTTPRequest;
// HTTP response structure
typedef struct {
char *body;
int code;
int length;
} HttpResponse;
#if R2_VERSION_NUMBER < 60000
#error Your radare2 is too old
#endif
-1134
View File
File diff suppressed because it is too large. Load diff