Compare commits

..
92 Commits
Author SHA1 Message Date
pancake 83b96f7a3e fiif 2025-10-03 18:21:20 +02:00
pancake e2ee8cdbb6 rjo 2025-10-03 17:36:29 +02:00
pancake 8630e95d83 morebecnh 2025-10-03 17:06:20 +02:00
pancake e07293e40f Initial benchmark working 2025-10-03 16:48:56 +02:00
pancake 415c546f92 Bench 2025-10-03 16:01:31 +02:00
pancake 8446af7385 Add description to all the config vars 2025-10-03 15:35:48 +02:00
pancake 5a156a0126 Do not use temperature for the openai provider in decai 2025-10-03 15:35:43 +02:00
pancake a28efa7a33 Add config var descriptions (fixes e??) 2025-10-03 12:46:25 +02:00
pancake bc5adb3fcb Refactor the model listing logic in decai 2025-10-02 14:00:30 +02:00
pancake 4bd1d911e0 Bring back the XAI Provider 2025-10-02 13:28:17 +02:00
pancake 41c9734c69 Bring back the mistral provider 2025-10-02 13:25:31 +02:00
pancake 1a41298dd6 Bring back the gemini provider 2025-10-02 13:22:47 +02:00
pancake c618f8ed09 Bring back the readable prompts 2025-10-02 13:20:42 +02:00
pancake 48932c5c64 Add support for listing models from openai endpoints in the decai 2025-10-02 13:17:08 +02:00
pancake 4da20a5c33 Bring back the OpenAI backend for Decai 2025-10-02 11:23:21 +02:00
pancake b7f94ad419 Release 1.1.1 2025-10-01 15:48:01 +02:00
pancake 2e45fb9e9c Fix spaces 2025-10-01 15:44:20 +02:00
pancake 9775e94455 Add support for ollamacloud to decai 2025-10-01 12:16:12 +02:00
pancake 67416766e2 Move the benchmark scripts into its own directory 2025-10-01 12:07:42 +02:00
pancake 5c1be40048 Improve benchmark2html.sh and makefile 2025-10-01 11:00:13 +02:00
pancake 49e87c7b7a Add script to generate benchmark results in html 2025-10-01 10:22:37 +02:00
pancake 11e7c288e0 Include decai.r2.js in the release page 2025-10-01 02:37:59 +02:00
pancake 909f4738f8 Release 1.1.0 2025-10-01 02:33:36 +02:00
pancake 0148a64aa7 Add decai to the release pack 2025-10-01 00:17:34 +02:00
Konstantinos Xynos d8df27a4c7 Refactored decai.r2.js to reuse providers implementations
* Update decai.r2.js to support local gpt4all
* Added support for local API server gpt4all by Nomic
* Added maxTokens support to GPT4all
* Added to GPT4all, temperature as 0.1 to enable deterministic option
* Rafactored using Claude. Needs to be tested
2025-10-01 00:09:18 +02:00
pancake 6608dbcf3c Add the Windows and macOS CI builds 2025-10-01 00:07:56 +02:00
pancake ef83ddefc9 Add r2 badge 2025-09-30 13:12:16 +02:00
pancake c19fd205e3 Make in root wraps the one in src 2025-09-30 12:51:39 +02:00
pancake eec673f8db Improved readme again 2025-09-30 12:29:27 +02:00
pancake 5e20b37e08 Remove python lint ci jobs 2025-09-30 12:15:59 +02:00
pancake 547418adf0 Move python and server stuff into the Attic 2025-09-30 12:00:23 +02:00
pancake e161577bcb Update README 2025-09-30 12:00:17 +02:00
pancake ba186d7c57 Move the getapikey logic into a separate function in r2ai.c 2025-09-26 03:08:49 +02:00
pancake 24c1e3fcb4 Suggest setting the API key with env var instead of the deprecated eval vars 2025-09-25 19:06:45 +02:00
pancake 1f001080ff Use r2-6.0.2 2025-08-20 01:38:51 +02:00
pancake a189a682b6 Disable the python ci job 2025-08-19 21:04:03 +02:00
cryptax a0299f791d Add support for gpt-5 2025-08-19 12:09:46 +02:00
cryptaxandcryptax 79214fb22c Fix to support lm-studio servers with openai style api and baseurl (#196)
Co-authored-by: cryptax <axelle@alligator>
2025-08-18 20:43:31 +02:00
cryptax 775d3243e6 removing port 2025-08-18 16:32:42 +02:00
pancake cd514ac099 Run indent 2025-08-18 12:24:58 +02:00
cryptax 6f352e1966 Support r2ai.baseurl having a custom port 2025-08-18 12:24:58 +02:00
cryptax 32458a653e Documentation for r2ai.port 2025-08-18 12:24:58 +02:00
cryptax b32fd6097f Fix Ollama calls using /chat instead of /chat/completions 2025-08-18 12:24:58 +02:00
cryptax df2fc67e2c Fixing list models for Ollama case 2025-08-18 12:24:58 +02:00
pancake b318c30865 Fix support for gpt-5 in decai 2025-08-14 14:27:08 +02:00
pancake 2610309b58 Release r2ai-1.0.0 2025-08-01 13:19:29 +02:00
pancake 0b81158d01 Rebrand r2ai.host into r2ai.baseurl in r2ai.c 2025-08-01 13:16:52 +02:00
pancake 4bcd2c582d Replace host/port configs with baseurl in decai 2025-08-01 13:08:15 +02:00
pancake 51aac5f348 Rename r2ai.base_url to r2ai.host for consistency with decai 2025-07-11 20:37:34 +02:00
pancake a437938526 Fix r2ai.c for the latest r2 2025-07-11 12:48:34 +02:00
pancake c6bba6c7dd Minor code improvements 2025-07-01 18:55:00 +02:00
pancake 3103df8210 Fix build 2025-06-27 21:38:22 +02:00
pancake b1fd4aab8e Honor use_files in system backend 2025-06-27 17:04:18 +02:00
pancake 26ca088c32 Fix build for old r2 2025-06-27 09:15:41 +02:00
pancake 710eb93bbe More build fixes 2025-06-27 00:26:20 +02:00
pancake fb1f53a176 More oldr2 fixes 2025-06-26 20:12:14 +02:00
pancake 138fd5e7d3 More build fixes for old r2s 2025-06-26 19:54:33 +02:00
pancake bd040f9a48 More build fixes 2025-06-26 19:26:27 +02:00
cryptax 1f3f04d74b documenting options in r2ai src 2025-06-26 19:23:32 +02:00
cryptax a7cff9d970 adding possibility to ask a question on a given function in direct mode 2025-06-26 19:23:27 +02:00
cryptax a1de8828ac todo to fix completely edition of commands 2025-06-26 19:23:23 +02:00
cryptax 7a1d4668d6 minor debug log message modif 2025-06-26 19:23:19 +02:00
cryptax e20f390878 Fix: auto.init_commands may be edited interactively by the user so the real edited command must be shown in the context 2025-06-26 19:23:09 +02:00
cryptax be18fe91ad fixed context reset + remove max_input_tokens: tokens are not characters we cant just truncate does not make sense 2025-06-26 19:21:56 +02:00
cryptax 6df2a231eb Fixing max output tokens were not correctly read and set in Anthropic requests 2025-06-26 19:21:51 +02:00
pancake 668eee7807 Fix build for old versions of r2 2025-06-26 19:20:19 +02:00
pancake 111974071f Fix command injection vuln in decai causing syntax errors in auto responses 2025-06-26 14:46:12 +02:00
pancake b193caec61 Add http.files and system curl option for r2ai.c 2025-06-26 14:31:56 +02:00
pancake 8994415242 Fix build with latest r2 2025-06-26 05:15:52 +02:00
pancake 7e6953dce6 Add abi version for r2 >= 5.9.9 2025-06-21 00:45:46 +02:00
cryptax 0bcd68c434 adding max_input_tokens to auto 2025-06-20 15:29:39 +02:00
pancake 74e0b23ab0 Improve the auto repl with better help and control actions 2025-06-17 12:42:55 +02:00
pancake 1044bea8dd Use r2pipe2 for auto mode to capture stderr 2025-06-17 00:30:09 +02:00
pancake 081e34a835 Improved auto prompt 2025-06-16 23:33:02 +02:00
pancake 9f22b4c3c3 Simplify the auto prompt 2025-06-16 22:56:24 +02:00
pancake 4fb8ab0f2e Add DecaiTTS, Improve reasoning, auto prompt and planning 2025-06-16 22:38:35 +02:00
sha0coder 8e5b7e0152 Dockerfile for r2 + r2ai + decai + r2ghidra + nice .radare2rc (#181) 2025-06-10 00:09:40 +02:00
pancake e40c655013 Rename ask_to_execute to yolo in r2ai-c 2025-06-09 20:12:07 +02:00
cryptax 0ebda776cf remove static lists for providers where dynamic fetch works fine 2025-06-07 12:54:36 +02:00
daniel nakov 879592f22e Add -e model=? to list models 2025-06-04 20:08:49 +02:00
pancake fd3efa6c70 Use the claude endpoint instead of hardcoding available models 2025-06-03 22:21:21 +02:00
pancake b420645c20 Add local YOLO mode in decai auto 2025-05-28 18:56:32 +02:00
pancake 211e9af847 Support broken jsons in decai auto 2025-05-28 18:55:01 +02:00
pancake 58e1eb6d14 Add 'skip' action and improve prompt in decai auto 2025-05-28 17:55:25 +02:00
pancake c72fe14f19 Fix gemini support in decai 2025-05-28 17:43:30 +02:00
pancake 10162f7302 Add decai -e files to use files instead of argv 2025-05-28 17:43:16 +02:00
pancake 949e22fdf6 Add o3 and o4 models in decai 2025-05-28 16:43:52 +02:00
pancake 2355c15cd5 gpt4.1 fix with wrong json for decai auto 2025-05-28 16:23:43 +02:00
pancake c5db441a07 List last claude models in decai 2025-05-26 12:03:42 +02:00
pancake 9853a99bcb Add skynet-like r2ai images 2025-05-15 00:59:55 +02:00
pancake 4d685520b1 Improve ollama error message when a model is not available 2025-05-14 20:02:51 +02:00
pancake a20035fd31 Workaround for the empty dir.tmp bug in r2 2025-05-14 12:56:53 +02:00
85 changed files with 4131 additions and 1822 deletions

No files matched your search

+142 -78
View File
@@ -1,87 +1,151 @@
# TODO: add windows
name: ci
name: CI and Release
env:
R2V: 5.9.8
R2V: 6.0.4
on:
push:
pull_request:
workflow_dispatch:
branches:
- '**'
tags:
- '[0-9]*'
# pull_request:
jobs:
build-linux:
runs-on: ubuntu-24.04
build:
name: Build (${{ matrix.os }} / ${{ matrix.build_system }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-latest, windows-latest]
build_system: [make, meson]
exclude:
- os: windows-latest
build_system: make
steps:
- uses: actions/checkout@v4
- name: Installing radare2
run: |
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
sudo dpkg -i "radare2_${R2V}_amd64.deb"
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
- name: Building the C rewrite
run: make -C src
- name: Running r2ai from r2
run: r2 -q -c 'r2ai -h' --
lint-python:
runs-on: ubuntu-24.04
- name: Checkout
uses: actions/checkout@v4
- name: Setup Meson/Ninja
if: matrix.build_system == 'meson'
run: |
python -m pip install --upgrade pip
pip install meson ninja
- name: Install radare2 on Linux
if: matrix.os == 'ubuntu-24.04'
run: |
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
sudo dpkg -i "radare2_${R2V}_amd64.deb"
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
- name: Install radare2 on macOS
if: matrix.os == 'macos-latest'
run: |
git clone --depth=1 --branch "${{env.R2V}}" https://github.com/radareorg/radare2
cd radare2
sys/install.sh
- name: Install radare2 on Windows
if: matrix.os == 'windows-latest'
shell: powershell
run: |
$url = "https://github.com/radareorg/radare2/releases/download/${{ env.R2V }}/radare2-${{ env.R2V }}-w64.zip"
$dest = "$env:TEMP\radare2.zip"
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
$prefix = 'C:\radare2'
if (Test-Path $prefix) { Remove-Item -Path $prefix -Recurse -Force }
Expand-Archive -Path $dest -DestinationPath $prefix -Force
# Move contents from subdirectory if present
$subdirs = Get-ChildItem -Path $prefix -Directory
if ($subdirs.Count -eq 1) {
$subdir = $subdirs[0]
Get-ChildItem -Path $subdir.FullName | Move-Item -Destination $prefix -Force
Remove-Item -Path $subdir.FullName -Recurse -Force
}
- name: Build with Make
if: matrix.build_system == 'make'
run: make -C src
- name: Build with Meson (Unix)
if: matrix.build_system == 'meson' && matrix.os != 'windows-latest'
run: |
cd src
meson setup build -Dr2_prefix=${{ secrets.R2_PREFIX }} || meson configure build
meson compile -C build
- name: Build with Meson (Windows)
if: matrix.build_system == 'meson' && matrix.os == 'windows-latest'
shell: powershell
run: |
cd src
$prefix = 'C:\radare2'
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
if ($LASTEXITCODE -ne 0) { meson configure build }
meson compile -C build
- name: Create release artifacts (tags only)
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
mkdir -p release
cp -rf src/r2ai.dll src/r2ai.exe src/r2ai src/r2ai.dylib src/r2ai.so release/ || true
cp -rf src/build/r2ai src/build/r2ai.dylib src/build/r2ai.so release/ || true
cp -f decai/decai.r2.js release/ || true
if command -v zip >/dev/null 2>&1; then
zip -j "release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip" release/*
else
powershell.exe -Command "Compress-Archive -Path release/* -DestinationPath 'release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip'"
fi
- name: Upload artifacts (tags only)
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-${{ matrix.build_system }}-binary
path: release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip
# - name: Smoke test
# run: |
# if command -v r2 >/dev/null 2>&1; then
# r2 -q -c 'r2ai -h' -- || true
# fi
release:
name: Create Release
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Installing radare2
run: |
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
sudo dpkg -i "radare2_${R2V}_amd64.deb"
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
- name: Setting up Python
run: |
cd py
python -m venv venv
. venv/bin/activate
pip install .
pip install pylint
cd ..
- name: Linting
run: make -C py cilint
build-python:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Installing radare2
run: |
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
sudo dpkg -i "radare2_${R2V}_amd64.deb"
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
- name: Setting up Python
run: |
cd py
python -m venv venv
. venv/bin/activate
pip install pylint
cd ..
- name: Install Python dependencies
run: |
cd py
. venv/bin/activate
pip install .
cd ..
- name: Running r2ai from the shell
run: |
cd py
. venv/bin/activate
python -m r2ai.cli -h
cd ..
- name: Running r2ai from r2pipe
run: |
cd py
. venv/bin/activate
r2 -q -c '#!pipe python -m r2ai.cli -h' /bin/ls
cd ..
- name: Running r2ai from r2
run: |
cd py
. venv/bin/activate
r2 -q -i r2ai/plugin.py -c r2ai /bin/ls
cd ..
- name: Checkout
uses: actions/checkout@v4
- name: Verify tag is on master
run: |
git fetch origin master:refs/remotes/origin/master
if ! git branch -r --contains ${GITHUB_SHA} | grep -q "origin/master"; then
echo "Tag commit is not on origin/master — skipping release."
exit 0
fi
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
- name: Add decai.r2.js to release artifacts
run: cp decai/decai.r2.js release-artifacts/
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
release-artifacts/**/*.zip
release-artifacts/decai.r2.js
generate_release_notes: true
-19
View File
@@ -1,19 +0,0 @@
name: Create release
on:
push:
tags:
- '*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
draft: false
prerelease: false
generate_release_notes: true
+6 -1
View File
@@ -1,4 +1,8 @@
build
b
src/b
src/r2ai
src/r2ai.dSYM
venv
**/__pycache__
*.pyc
@@ -6,4 +10,5 @@ venv
*.o
*.so
*.dylib
*.a
*.a
third_party
View File
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
View File
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4 -17
View File
@@ -1,17 +1,4 @@
.PHONY: all all.old deps clean deps-global pub lint cilint
.PHONY: install uninstall user-install user-uninstall
all:
@echo "Usage: Run 'make' in the following subdirectories instead"
@echo "src/ - Modern C rewrite in form of a native r2 plugin"
@echo "py/ - The old Python cli and r2 plugin"
@echo "decai/ - r2js plugin with focus on decompiling"
@echo "server/ - shellscript to easily run llamacpp and other"
@false
clean:
@echo We are clean already
mrproper:
$(MAKE) clean
TARGETS=all clean mrproper user-install install user-uninstall uninstall
$(TARGETS):
$(MAKE) -C src $@
.PHONY: $(TARGETS)
+56 -80
View File
@@ -1,120 +1,96 @@
```
,______ .______ .______ ,___
: __ \ \____ |: \ : __|
| \____|/ ____|| _,_ || : |
| : \ \ . || : || |
| |___\ \__:__||___| || |
|___| : |___||___|
*
# R2AI - Augmented reversing with LLM for radare2
```console
╭─────────────────────────────────╮
│ ,______ .______ .______ ,___ │
╭──╮ │ : __ \ \____ |: \ : __| │
│ _│_ │ | \____|/ ____|| _,_ || : | │
│ O O < | : \ \ . || : || | │
│ │╷ │ | |___\ \__:__||___| || | │
│ ││ │ |___| : |___||___| │
│ ─╯│ ╰─────────────────────────────────╯
╰───╯
```
[![ci](https://github.com/radareorg/r2ai/actions/workflows/ci.yml/badge.svg)](https://github.com/radareorg/r2ai/actions/workflows/ci.yml)
Run a language model to entertain you or help answering questions about radare2 or reverse engineering in general. The language model may be local (running without Internet on your host) or remote (e.g if you have an API key). Note that models used by r2ai are pulled from external sources which may behave different or respond unreliable information. That's why there's an ongoing effort into improving the post-finetuning using memgpt-like techniques which can't get better without your help!
<p align="center">
<img src="doc/r2clippy.jpg">
</p>
[![radare2](https://img.shields.io/badge/radare2-6.0.4-green)](https://github.com/radareorg/radare2)
## Components
R2AI repository contains four different projects:
This repository contains two plugins for radare2:
Recommended plugins:
* **r2ai** - native plugin for radare2
* **decai** - r2js plugin with special focus on decompilation
* **r2ai-plugin** (`src/` directory)
* Native plugin written in C
* adds r2ai command inside r2
* **decai** (r2js plugin focus on decompilation)
* adds 'decai' command to the r2 shell
* talks to local or remote services with curl
* focus on decompilation
If you are looking to use radare2 with other agents via MCP:
Deprecated implementations:
* **r2ai-python** cli tool (`py/` directory)
* r2-like repl using r2pipe to comunicate with r2
* supports auto solving mode
* client and server openapi protocol
* download and manage models from huggingface
* **r2ai-server**
* favour *ollama* instead
* list and select models downloaded from r2ai
* simple cli tool to start local openapi webservers
* supports llamafile, llamacpp, r2ai-w and kobaldcpp
* **r2mcp** - the [official radare2 mcp](https://github.com/radare2/radare2-mcp)
* **r2copilot** - the mcp with focus on CTF [r2copilot](https://github.com/darallium/r2-copilot)
## Features
* Support Auto mode to solve tasks using function calling
* Use local and remote language models (llama, ollama, openai, anthropic, ..)
* Support OpenAI, Anthropic, Bedrock
* Index large codebases or markdown books using a vector database
* Slurp file and perform actions on that
* Embed the output of an r2 command and resolve questions on the given data
* Define different system-level assistant role
* Set environment variables to provide context to the language model
* Configure different roles and customize prompts
* Scriptable via r2pipe via the r2ai command
* Live with repl and batch mode from cli or r2 prompt
* Scriptable via r2pipe
* Use different models, dynamically adjust query template
* Load multiple models and make them talk between them
* Support Automatic (ReAct) mode to solve tasks using function calling
* Use local and remote language models (ollama, openai, grok, anthropic, ..)
* RAG markdown, code or textfiles using its native vector database
* Embed the output of an r2 command and resolve questions on the given data
## Practical Examples
* Enhanced decompilation with `r2ai -d`
* Autoname functions with `r2ai -n`
* Explain function with `r2ai -x`
* Find vulnerabilities with `r2ai -V`
## Installation
### Radare2 Package Manager
The recommended way to install any of the r2ai components is via r2pm:
You can find all the packages with `r2pm -s r2ai`:
```console
$ r2pm -s r2ai
r2ai-py run a local language model integrated with radare2
r2ai-py-plugin r2ai plugin for radare2
r2ai-plugin r2ai plugin rewritten in plain C
r2ai-server start a language model webserver in local
decai r2ai r2js subproject with focus on LLM decompilation for radare2
$
$ r2pm -Uci r2ai
$ r2pm -Uci decai
```
### From sources
## Using r2ai
Running `make` on the root directory will instruct you where the sub-projects are, just run the `install`/`user-install` targets in there.
- Adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai`
- You can also run the wrapper in $PATH: `r2pm -r r2ai`
Drop your API keys in environment variables or files in your home:
```console
$ make
Usage: Run 'make' in the following subdirectories instead
src/ - Modern C rewrite in form of a native r2 plugin
py/ - The old Python cli and r2 plugin
decai/ - r2js plugin with focus on decompiling
server/ - shellscript to easily run llamacpp and other
$
$ cat ~/.r2ai.anthropic-key
sk-ant-api03-CENSORED
$ export OPENAI_API_KEY=sk-proj-6rlSPS-zN1v...
```
## Running r2ai
### Launch r2ai
- The r2ai-plugin adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai-r`
- If you installed via r2pm, you can execute it like this: `r2pm -r r2ai`
- Otherwise, `./r2ai.sh [/absolute/path/to/binary]`
If you have an **API key**, put it in the adequate file:
| AI | API key |
| --------- | -------------------------- |
| OpenAI | `$HOME/.r2ai.openai-key` |
| Gemini | `$HOME/.r2ai.gemini-key` |
| Anthropic | `$HOME/.r2ai.anthropic-key` |
| Mistral | `$HOME/.r2ai.mistral-key` |
...
Example using an Anthropic API key:
## Saving settings
You may customize and save your configuration settings using your OS's default settings file (e.g `~/.radare2rc` on Linux).
For example, the following configuration sets Claude 3.7 by default, with max output tokens to 64000.
```
$ cat ~/.r2ai.anthropic-key
sk-ant-api03-CENSORED
r2ai -e api=anthropic
r2ai -e model=claude-3-7-sonnet-20250219
r2ai -e max_tokens=64000
```
## Documentation
* There's [a chapter](https://book.rada.re/plugins/r2ai.html) in the official r2book
* Cryptax on [lmstudio+gptoss](https://cryptax.medium.com/r2ai-with-lmstudio-and-gpt-oss-08efa5ea2476) blog post
* Malware analysis [with r2ai](https://github.com/cryptax/talks/blob/master/BSidesKristiansand-2025/r2ai.pdf) by Cryptax
## Videos
- https://infosec.exchange/@radareorg/111946255058894583
+1
View File
@@ -0,0 +1 @@
1.1.1
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
vim src/r2ai.c
vim VERSION
+3
View File
@@ -4,6 +4,9 @@ SPD=$(shell r2 -H R2_LIBR_PLUGINS)
BF=a.out
all:
@echo "Run: make user-install"
test:
@echo 'You can now use 'decai' command'
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
+1205 -1115
View File
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
FROM ubuntu:24.04
WORKDIR /root/
RUN apt update && apt install -y build-essential
RUN apt install -y git curl pkg-config
RUN apt install -y python3.12
RUN apt install -y python3.12-venv
RUN apt install -y zlib1g zlib1g-dev
RUN apt install -y unzip neovim
RUN apt install -y ninja-build meson
# install radare
RUN git clone https://github.com/radareorg/radare2.git
WORKDIR radare2/
RUN sys/install.sh
# install plugins
WORKDIR /root/
RUN r2pm -U
#RUN r2pm -ci r2ghidra r2ghidra-sleigh # slow and optional
RUN r2pm -ci r2ai-server r2ai-plugin # r2ai-py is deprecated
RUN r2pm -ci decai r2dec
# r2 tunning
RUN echo 'e io.unalloc=true' >> /root/.radare2rc
RUN echo 'e cfg.fortunes=false' >> /root/.radare2rc
RUN echo 'e emu.str=true' >> /root/.radare2rc
RUN echo 'e bin.relocs.apply=true' >> /root/.radare2rc
RUN echo 'e dir.tmp=/tmp/' >> /root/.radare2rc
RUN echo 'e scr.prompt.flag=1' >> /root/.radare2rc
RUN echo 'e cfg.editor=nvim' >> /root/.radare2rc
RUN echo 'decai -e deterministic=true' >> /root/.radare2rc
RUN echo 'decai -e lang=Python' >> /root/.radare2rc
RUN echo 'decai -e cmds=pdd,pdg' >> /root/.radare2rc
RUN echo 'decai -e prompt=..' >> /root/.radare2rc
RUN echo 'decai -e api=ollama' >> /root/.radare2rc
RUN echo 'decai -e model=qwen2.5-coder:latest' >> /root/.radare2rc
# Uncomment to have ollama inside the docker (will not use gpu)
#RUN curl -fsSL https://ollama.com/install.sh | sh
#RUN \
#  echo '#!/bin/bash'; \
#  echo 'ollama serve &'; \
#  echo 'sleep 5'; \
#  echo 'ollama pull qwen2.5-coder:latest'; >> /root/ollama.run
#
#RUN chmod +x /root/ollama.run
#RUN /root/ollama.run
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (https://www.inkscape.org/) -->
<svg
id="svg3004"
version="1.1"
inkscape0:version="0.48.2 r9819"
width="500"
height="300"
xml:space="preserve"
sodipodi0:docname="r2.svg"
sodipodi:docname="r2ai-solid-black.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:ns0="https://creativecommons.org/ns#"
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
showguides="true"
inkscape:zoom="1.207"
inkscape:cx="170.25684"
inkscape:cy="153.27258"
inkscape:window-width="1714"
inkscape:window-height="845"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg3004" /><metadata
id="metadata3010"><RDF><ns0:Work
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
id="defs3008"><pattern
inkscape:collect="always"
xlink:href="#pattern6"
preserveAspectRatio="xMidYMid"
id="pattern7"
patternTransform="translate(-3.203125e-6)" /><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3018"><path
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
id="path3020"
inkscape0:connector-curvature="0" /></clipPath><pattern
patternUnits="userSpaceOnUse"
width="1.2374369"
height="110.30865"
patternTransform="translate(295.04028,-128.87021)"
preserveAspectRatio="xMidYMid"
id="pattern6"><rect
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
id="rect6"
width="1.2374369"
height="110.30865"
x="0"
y="0"
ry="0.61871845" /></pattern><marker
markerWidth="1.2374369"
markerHeight="110.30865"
refX="0.61871845"
refY="55.154324"
orient="auto"
id="marker7"><rect
style="fill:url(#pattern7);stroke:none"
width="1.2374369"
height="110.30865"
x="0"
y="0"
id="rect7" /></marker></defs><sodipodi0:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape0:pageopacity="0"
inkscape0:pageshadow="2"
inkscape0:window-width="791"
inkscape0:window-height="818"
id="namedview3006"
showgrid="false"
inkscape0:zoom="0.39912236"
inkscape0:cx="715.69007"
inkscape0:cy="676.77258"
inkscape0:window-x="2687"
inkscape0:window-y="256"
inkscape0:window-maximized="0"
inkscape0:current-layer="g3012" /><path
id="path3496-8-6-5-6-6-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
sodipodi:nodetypes="ccccccccccccccc" /><path
id="path3496-8-6-5-6-6-2-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
sodipodi:nodetypes="ccccccccccccccc" /><path
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
style="fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
id="path3492-8-7"
inkscape0:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+112
View File
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (https://www.inkscape.org/) -->
<svg
id="svg3004"
version="1.1"
inkscape0:version="0.48.2 r9819"
width="500"
height="300"
xml:space="preserve"
sodipodi0:docname="r2.svg"
sodipodi:docname="r2ai-solid-white.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:ns0="https://creativecommons.org/ns#"
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
showguides="true"
inkscape:zoom="1.207"
inkscape:cx="170.25684"
inkscape:cy="153.27258"
inkscape:window-width="1714"
inkscape:window-height="845"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg3004" /><metadata
id="metadata3010"><RDF><ns0:Work
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
id="defs3008"><pattern
inkscape:collect="always"
xlink:href="#pattern6"
preserveAspectRatio="xMidYMid"
id="pattern7"
patternTransform="translate(-3.203125e-6)" /><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3018"><path
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
id="path3020"
inkscape0:connector-curvature="0" /></clipPath><pattern
patternUnits="userSpaceOnUse"
width="1.2374369"
height="110.30865"
patternTransform="translate(295.04028,-128.87021)"
preserveAspectRatio="xMidYMid"
id="pattern6"><rect
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
id="rect6"
width="1.2374369"
height="110.30865"
x="0"
y="0"
ry="0.61871845" /></pattern><marker
markerWidth="1.2374369"
markerHeight="110.30865"
refX="0.61871845"
refY="55.154324"
orient="auto"
id="marker7"><rect
style="fill:url(#pattern7);stroke:none"
width="1.2374369"
height="110.30865"
x="0"
y="0"
id="rect7" /></marker></defs><sodipodi0:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape0:pageopacity="0"
inkscape0:pageshadow="2"
inkscape0:window-width="791"
inkscape0:window-height="818"
id="namedview3006"
showgrid="false"
inkscape0:zoom="0.39912236"
inkscape0:cx="715.69007"
inkscape0:cy="676.77258"
inkscape0:window-x="2687"
inkscape0:window-y="256"
inkscape0:window-maximized="0"
inkscape0:current-layer="g3012" /><path
id="path3496-8-6-5-6-6-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
sodipodi:nodetypes="ccccccccccccccc" /><path
id="path3496-8-6-5-6-6-2-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
sodipodi:nodetypes="ccccccccccccccc" /><path
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
id="path3492-8-7"
inkscape0:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

+209
View File
@@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (https://www.inkscape.org/) -->
<svg
id="svg3004"
version="1.1"
inkscape0:version="0.48.2 r9819"
width="500"
height="300"
xml:space="preserve"
sodipodi0:docname="r2.svg"
sodipodi:docname="r2ai.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:ns0="https://creativecommons.org/ns#"
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
showguides="true"
inkscape:zoom="1.207"
inkscape:cx="170.25684"
inkscape:cy="153.27258"
inkscape:window-width="1714"
inkscape:window-height="845"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg3004" /><metadata
id="metadata3010"><RDF><ns0:Work
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
id="defs3008"><marker
style="overflow:visible"
id="Stop"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Stop"
markerWidth="2"
markerHeight="19"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none"><path
style="fill:none;stroke:context-stroke;stroke-width:1"
d="M 0,4 V -4"
id="path23" /></marker><pattern
inkscape:collect="always"
xlink:href="#pattern6"
preserveAspectRatio="xMidYMid"
id="pattern7"
patternTransform="translate(-3.203125e-6)" /><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3018"><path
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
id="path3020"
inkscape0:connector-curvature="0" /></clipPath><pattern
patternUnits="userSpaceOnUse"
width="1.2374369"
height="110.30865"
patternTransform="translate(295.04028,-128.87021)"
preserveAspectRatio="xMidYMid"
id="pattern6"><rect
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
id="rect6"
width="1.2374369"
height="110.30865"
x="0"
y="0"
ry="0.61871845" /></pattern><marker
markerWidth="1.2374369"
markerHeight="110.30865"
refX="0.61871845"
refY="55.154324"
orient="auto"
id="marker7"><rect
style="fill:url(#pattern7);stroke:none"
width="1.2374369"
height="110.30865"
x="0"
y="0"
id="rect7" /></marker><marker
style="overflow:visible"
id="Stop-3"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Stop"
markerWidth="1"
markerHeight="19"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none"><path
style="fill:none;stroke:context-stroke;stroke-width:1"
d="M 0,4 V -4"
id="path23-6" /></marker><marker
style="overflow:visible"
id="Stop-3-3"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Stop"
markerWidth="1"
markerHeight="19"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none"><path
style="fill:none;stroke:context-stroke;stroke-width:1"
d="M 0,4 V -4"
id="path23-6-5" /></marker><marker
style="overflow:visible"
id="Stop-6"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Stop"
markerWidth="2"
markerHeight="19"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none"><path
style="fill:none;stroke:context-stroke;stroke-width:1"
d="M 0,4 V -4"
id="path23-0" /></marker></defs><rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.755906"
id="rect5"
width="381.13055"
height="146.37111"
x="62.225395"
y="141.42136"
ry="0.6875" /><sodipodi0:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape0:pageopacity="0"
inkscape0:pageshadow="2"
inkscape0:window-width="791"
inkscape0:window-height="818"
id="namedview3006"
showgrid="false"
inkscape0:zoom="0.39912236"
inkscape0:cx="715.69007"
inkscape0:cy="676.77258"
inkscape0:window-x="2687"
inkscape0:window-y="256"
inkscape0:window-maximized="0"
inkscape0:current-layer="g3012" /><path
id="path3496-8-6-5-6"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
d="m 409.32718,13.961811 c 0,0 -62.86723,31.580382 -63.41792,31.850056 l 9.44739,17.835078 h 53.97053 z m -105.6385,52.816247 36.72686,18.363427 9.46691,-17.881604 -9.79387,-18.512997 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.174863 Z"
sodipodi:nodetypes="ccccccccccccccc" /><path
id="path3496-8-6-5-6-6"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
d="m 303.71834,13.970537 c 0,0 -62.86723,31.580387 -63.41792,31.850061 l 9.44739,17.835077 h 53.97053 z m -105.6385,52.816251 36.72686,18.363428 9.46691,-17.881604 -9.79387,-18.512998 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.183593 Z"
sodipodi:nodetypes="ccccccccccccccc" /><path
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3)"
d="m 303.83057,67.231304 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657"
id="path7-5"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><path
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3-3)"
d="m 409.35891,66.9637 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 L 304,66.43346"
id="path7-5-2"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><g
id="g5"><path
id="path3496-8-6-5-6-6-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
d="m 306.19367,160.96204 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 V 217.1751 Z"
sodipodi:nodetypes="ccccccccccccccc" /><path
style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop)"
d="m 306.73753,215.43843 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
id="path7-5-9"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><g
id="g4"><path
id="path3496-8-6-5-6-6-2-2"
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
d="m 417.07189,160.35585 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 v -49.42094 z"
sodipodi:nodetypes="ccccccccccccccc" /><path
style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-6)"
d="m 417.61575,214.83224 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
id="path7-5-9-6"
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><path
d="M 198.25651,22.933951 V 114.43684 H 209.67 v 5.16734 h -79.6648 v -5.16734 h 15.28587 V 64.918208 c 0,-8.826454 0.64651,-16.578901 -2.36609,-24.974984 -0.64651,-2.152817 -7.10304,-14.424164 -10.33467,-9.042602 0.86266,3.230669 1.29303,6.889785 1.29303,10.121413 0,14.422241 -9.90526,23.680027 -24.11424,23.680027 -14.855492,0 -25.189206,-10.333714 -25.189206,-25.189209 0,-15.501054 13.56438,-23.896176 27.773356,-23.896176 15.28299,0 27.55433,8.178976 34.66506,21.312988 h 0.42653 V 15.183424 c 20.66647,0.646518 41.55197,2.58415 62.22516,2.58415 v 5.166377 z"
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
id="path3492-8"
inkscape0:connector-curvature="0" /><path
d="m 201.6106,169.92546 v 91.50289 h 11.41349 v 5.16734 h -79.6648 v -5.16734 h 15.28587 v -49.51864 c 0,-8.82645 0.64651,-16.5789 -2.36609,-24.97498 -0.64651,-2.15282 -7.10304,-14.42417 -10.33467,-9.0426 0.86266,3.23067 1.29303,6.88978 1.29303,10.12141 0,14.42224 -9.90526,23.68003 -24.11424,23.68003 -14.855492,0 -25.189206,-10.33372 -25.189206,-25.18921 0,-15.50106 13.564376,-23.89618 27.773356,-23.89618 15.28299,0 27.55433,8.17898 34.66506,21.31299 h 0.42653 v -21.74624 c 20.66647,0.64652 41.55197,2.58415 62.22516,2.58415 v 5.16638 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
id="path3492-8-7"
inkscape0:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 12 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+41
View File
@@ -0,0 +1,41 @@
SHELL=/bin/bash
PROVIDER=ollama
MODELS=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
MODELS=gemma3:1b
LANGS=Dart Perl Ruby Javascript PHP C
BINARIES=files/complex1 files/complex2 files/complex3
MODELS=gpt-4o
PROVIDER=openai
all:
mkdir -p tmp
for b in $(BINARIES) ; do \
for a in $(MODELS) ; do \
echo "$$b $$a"; \
./run.sh $$b $$a ${PROVIDER} ${LANGS} > tmp/$${b##*/}_$${a//\//_}.html ; \
done ; \
done
.PHONY: run index evaluate
compile: $(BINARIES)
files/complex1: files/complex1.c
gcc -o $@ $<
files/complex2: files/complex2.c
gcc -o $@ $<
files/complex3: files/complex3.c
gcc -o $@ $<
lmstudio:
for b in $(BINARIES) ; do \
for a in $(MODELS) ; do \
./run.sh $$b $$a lmstudio ${LANGS} > $${b##*/}_$${a//\//_}.html ; \
done ; \
done
clean:
rm -rf tmp
+4
View File
@@ -0,0 +1,4 @@
<b>
decai -e model=$a;decai -e lang=$l;s 0x100000924;af;decai -d
</b><br/>
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
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
# Evaluate decompiled outputs using mai and determine best model+lang
echo "Evaluating decompiled outputs..."
tmpfile=$(mktemp)
MAI="mai -q -p openai -m gpt-4o"
# MAI="mai -q"
for orig in files/complex1.c files/complex2.c files/complex3.c; do
base=$(basename $orig .c)
original_code=$(cat $orig)
for f in tmp/${base}_*.txt; do
if [ -f "$f" ]; then
decompiled_code=$(cat $f)
#echo "Comparing $orig $f"
result=`echo n | ${MAI} -q -r "/template files/CMPROMPT.txt original=@${orig} decompiled=@${f}"`
score=`echo "$result" | head -n 1 | grep -oE '[0-9]+'` # |head -n 1`
perfect=`echo "$result" | grep 'No sign' && echo yes`
[ -z "$score" ] && score=0
if [ "$perfect" = yes ]; then
score=100
else
score="$((0+${score}))"
fi
echo "$score\t$f"
if [ -n "$score" ]; then
key="${f%.*}" # remove .txt
# Check if key exists in tmpfile
existing=$(grep "^$key " $tmpfile | awk '{print $2}')
if [ -n "$existing" ]; then
new_score=$(( existing + score ))
sed -i "s/^$key .*/$key $new_score/" $tmpfile
else
echo "$key $score" >> $tmpfile
fi
fi
fi
done
done
# Find the best (highest total score)
best_key=""
best_score=0
while read key score; do
if [ "$score" -gt "$best_score" ]; then
best_score=$score
best_key=$key
fi
done < $tmpfile
rm $tmpfile
if [ -n "$best_key" ]; then
echo "Best model+language combination: $best_key with total score $best_score"
else
echo "No scores found"
fi
+17
View File
@@ -0,0 +1,17 @@
Compare the two provided code in C with the decompiled counterpart. Ignore function or variable names, syntax differences, language-specific constructs, and formatting. Focus solely on what the code does: the operations performed, control flow, logic, and functional behavior. Determine if they are functionally equivalent in terms of their effects and computations.
Output only:
- A numeric score from 1 to 100 (100 being identical logic and functionality)
- Followed by a single sentence describing the most relevant semantic differences (or "No significant semantic differences" if score is 95+)
**Original Source Code:**
```C
{original}
```
**Decompiled Code:**
```
{decompiled}
```
+21
View File
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int *arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) return 1;
for (int i = 0; i < n; i++) {
arr[i] = i * i;
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
for (int j = 0; j < i; j++) {
sum -= j;
}
}
printf("Sum: %d\n", sum);
free(arr);
return 0;
}
+39
View File
@@ -0,0 +1,39 @@
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
int main() {
struct Node *head = NULL;
struct Node *tail = NULL;
for (int i = 0; i < 10; i++) {
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
if (newNode == NULL) return 1;
newNode->data = i * 2;
newNode->next = NULL;
if (head == NULL) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
}
struct Node *current = head;
int sum = 0;
while (current != NULL) {
sum += current->data;
current = current->next;
}
printf("Sum: %d\n", sum);
current = head;
while (current != NULL) {
struct Node *temp = current;
current = current->next;
free(temp);
}
return 0;
}
+33
View File
@@ -0,0 +1,33 @@
#include <stdio.h>
int main() {
int x = 42;
int y = 0;
switch (x & 15) {
case 0:
y = x << 1;
break;
case 1:
y = x >> 1;
break;
case 2:
y = x ^ 255;
break;
case 3:
y = ~x;
break;
default:
y = x | 128;
break;
}
if ((y & 1) == 0) {
y += 10;
} else {
y -= 5;
}
for (int i = 0; i < 5; i++) {
y = (y << 2) | (y >> 30);
}
printf("Result: %d\n", y);
return 0;
}
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
cp decai/BENCH.txt tmp/BENCH.txt
# Find all directories containing BENCH.txt
dirs=$(find . -name "BENCH.txt" -type f | xargs dirname | sort | uniq)
# Start HTML output
cat <<EOF > benchmark_index.html
<html>
<table>
EOF
# Process each directory
for dir in $dirs; do
cat <<EOF >> benchmark_index.html
<tr> <td>
<div>
EOF
# Add contents of BENCH.txt
cat "$dir/BENCH.txt" >> benchmark_index.html
cat <<EOF >> benchmark_index.html
</div>
</td>
<td>
EOF
# Find and add links to HTML files
for html_file in $(ls "$dir"/*.html 2>/dev/null | sort); do
base=$(basename "$html_file" .html)
cat <<EOF >> benchmark_index.html
<a href="$dir/$base.html">$base</a>
<br />
EOF
done
cat <<EOF >> benchmark_index.html
</td>
</tr>
EOF
done
# End HTML output
cat <<EOF >> benchmark_index.html
</table>
<br />
<br />
</html>
EOF
+231
View File
@@ -0,0 +1,231 @@
#!/bin/bash
# Generate HTML output for command executions with collapsible sections and width control
# If PLAIN=1, output plain text instead
if [ $# -lt 4 ]; then
echo "Usage: $0 <binary> <model> <provider> <lang1> [lang2 ...]"
echo "Or: $0 command1 [command2 ...] (legacy)"
exit 1
fi
# Check if first arg is a file (binary), assume new format
if [ -f "$1" ]; then
binary="$1"
model="$2"
provider="$3"
shift 3
langs=("$@")
commands=()
for lang in "${langs[@]}"; do
# commands+=("# $lang echo \"decai -e model=$model;decai -e lang=$lang;decai -e api=$provider;af;decai -d\" | r2 -q $binary 2> /dev/null")
commands+=("# $lang r2 -e scr.color=0 -qcq -qc \"decai -e model=$model;decai -e lang=$lang;decai -e api=$provider;af;decai -d\" $binary")
done
set -- "${commands[@]}"
else
# Legacy mode
commands=("$@")
fi
if [ "$PLAIN" = "1" ]; then
# In plain mode, execute each command and print raw output.
# Also, if TXTFILE is set, save combined raw output there.
tmp_plain=$(mktemp)
for cmd in "$@"; do
if [[ "$cmd" =~ ^#[[:space:]]*(.*) ]]; then
exec_cmd="${BASH_REMATCH[1]}"
else
exec_cmd="$cmd"
fi
tmpout=$(mktemp)
eval "$exec_cmd" > "$tmpout" 2>&1 || true
mv "$tmpout" "$tmp_plain"
rm -f "$tmpout"
done
if [ -n "$TXTFILE" ]; then
mv "$tmp_plain" "$TXTFILE"
else
rm -f "$tmp_plain"
fi
exit 0
fi
# Timeout in seconds for each command (configurable via TIMEOUT environment variable)
TIMEOUT=${TIMEOUT:-30}
cat << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Command Execution Results</title>
<style>
* {
box-sizing: border-box;
}
:root {
--max-width: 400px;
}
body {
font-family: monospace;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
overflow-x: hidden;
}
.slider-container {
background: white;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
width: 100%;
}
.slider-container label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
.slider-container input {
width: 100%;
}
.commands-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
}
.command-section {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
width: var(--max-width);
}
.command-title {
background: #e9ecef;
padding: 12px 15px;
cursor: pointer;
border: none;
width: 100%;
text-align: left;
font-family: inherit;
font-size: inherit;
border-radius: 8px 8px 0 0;
position: relative;
}
.command-title:hover {
background: #dee2e6;
}
.command-time {
position: absolute;
top: 12px;
right: 15px;
color: #6c757d;
font-size: 0.9em;
}
.command-body {
padding: 15px;
border-top: 1px solid #dee2e6;
display: none;
}
.command-output {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-x: auto;
}
.command-section.expanded .command-body {
display: block;
}
</style>
</head>
<body>
<div class="slider-container">
<label for="width-slider">Maximum Width: <span id="width-value">400</span>px</label>
<input type="range" id="width-slider" min="200" max="2000" value="400" step="50">
</div>
<div class="commands-container">
EOF
# Process each command
for cmd in "$@"; do
# Extract title: if command starts with #, parse title and command, else use the command
if [[ "$cmd" =~ ^#[[:space:]]*([^[:space:]]+)[[:space:]]*(.*) ]]; then
title="${BASH_REMATCH[1]}"
exec_cmd="${BASH_REMATCH[2]}"
else
title="$cmd"
exec_cmd="$cmd"
fi
echo "RUN $exec_cmd" > /dev/stderr
# Execute command and time it
start_time=$(date +%s.%N)
tmpfile=$(mktemp)
# echo "eval $exec_cmd > $tmpfile" > /dev/stder
eval "$exec_cmd" > "$tmpfile" 2> /dev/null &
pid=$!
(sleep $TIMEOUT && kill $pid 2>/dev/null) &
wait $pid 2>/dev/null
output=$(cat "$tmpfile")
#echo "$output" > /dev/stderr
rm "$tmpfile"
exit_code=$?
end_time=$(date +%s.%N)
# Calculate execution time
execution_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
if [ $? -ne 0 ]; then
execution_time="N/A"
else
execution_time=$(printf "%.3f" "$execution_time")
fi
# Save plain output to .txt file if in new format
if [ -n "$binary" ]; then
txtfile="tmp/${binary##*/}_${model//\//_}_${title}.txt"
mkdir -p "$(dirname "$txtfile")" 2>/dev/null || true
echo "$output" > "$txtfile"
fi
# Escape HTML in output
#escaped_output=$(echo "$output" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
escaped_output="$output"
cat << EOF
<div class="command-section expanded">
<button class="command-title" onclick="toggleSection(this)">$title<span class="command-time">${execution_time}s</span></button>
<div class="command-body">
<div class="command-output">$escaped_output</div>
</div>
</div>
EOF
done
cat << 'EOF'
</div>
<script>
// Slider functionality
const slider = document.getElementById('width-slider');
const widthValue = document.getElementById('width-value');
slider.addEventListener('input', function() {
const value = this.value;
document.documentElement.style.setProperty('--max-width', value + 'px');
widthValue.textContent = value;
});
// Toggle functionality
function toggleSection(button) {
const section = button.parentElement;
section.classList.toggle('expanded');
}
</script>
</body>
</html>
EOF
BIN
View File
Binary file not shown.
+66
View File
@@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define ALPHABET_SIZE 26
// Function to perform substitution (both encryption and decryption)
void substitute(const char *input, const char *substitution, char *output, int reverse) {
char mapping[ALPHABET_SIZE];
if (reverse) {
// Create reverse substitution table for decryption
for (int i = 0; i < ALPHABET_SIZE; i++) {
mapping[substitution[i] - 'A'] = 'A' + i;
}
} else {
// Use the direct substitution table for encryption
for (int i = 0; i < ALPHABET_SIZE; i++) {
mapping[i] = substitution[i];
}
}
for (int i = 0; input[i] != '\0'; i++) {
char c = input[i];
// Handle uppercase letters
if (isupper(c)) {
output[i] = mapping[c - 'A'];
}
// Handle lowercase letters
else if (islower(c)) {
output[i] = tolower(mapping[c - 'a']);
}
// Leave non-alphabet characters unchanged
else {
output[i] = c;
}
}
output[strlen(input)] = '\0'; // Null-terminate the output string
}
int main() {
char input[256];
char output[256];
// Configurable substitution key (A-Z)
const char *substitution = "QWERTYUIOPASDFGHJKLZXCVBNM"; // Example substitution key
// Get input from the user
printf("Enter text to encrypt: ");
fgets(input, sizeof(input), stdin);
// Remove newline character from input if it exists
input[strcspn(input, "\n")] = 0;
// Perform encryption (forward substitution)
substitute(input, substitution, output, 0);
printf("Encrypted text: %s\n", output);
// Perform decryption (reverse substitution)
substitute(output, substitution, input, 1);
printf("Decrypted text: %s\n", input);
return 0;
}
-24
View File
@@ -1,24 +0,0 @@
@echo off
setlocal
:check_error
if %errorlevel% neq 0 (
echo Error encountered, exiting script.
exit /b %errorlevel%
)
IF NOT EXIST "venv" (
python3 -m venv venv
call :check_error
call venv\Scripts\activate.bat
call :check_error
pip install -r requirements.txt
call :check_error
) ELSE (
call venv\Scripts\activate.bat
call :check_error
)
python main.py
call :check_error
endlocal
+13 -6
View File
@@ -1,8 +1,13 @@
EXT=$(shell r2 -H R2_LIBEXT)
DOTEXE=
# DOTEXE=.exe
DOTLIB=.$(shell r2 -H R2_LIBEXT)
CFLAGS+=$(shell pkg-config --cflags r_core)
LDFLAGS+=$(shell pkg-config --libs r_core)
R2SYSPLUGDIR=$(shell r2 -H R2_LIBR_PLUGINS)
R2USRPLUGDIR=$(shell r2 -H R2_USER_PLUGINS)
R2BINDIR=$(shell r2pm -H R2PM_BINDIR)
CFLAGS+=-I. -fPIC
CFLAGS+=-Wall
CFLAGS+=-g
@@ -36,7 +41,8 @@ OBJS+=openai.o
OBJS+=markdown.o
all: $(OBJS) r2check
$(CC) -fPIC -shared -o r2ai.$(EXT) $(OBJS) $(CFLAGS) $(LDFLAGS)
$(CC) -fPIC -shared -o r2ai$(DOTLIB) $(OBJS) $(CFLAGS) $(LDFLAGS)
$(CC) -pie -o r2ai$(DOTEXE) $(OBJS) $(CFLAGS) $(LDFLAGS) -lm main.c
try: all
$(MAKE) user-install
@@ -53,16 +59,17 @@ indent:
user-install: user-uninstall
mkdir -p $(R2USRPLUGDIR)
cp -f r2ai.$(EXT) $(R2USRPLUGDIR)
cp -f r2ai$(DOTLIB) $(R2USRPLUGDIR)
cp -f r2ai$(DOTEXE) $(R2BINDIR)
user-uninstall:
rm -f $(R2USRPLUGDIR)/r2ai.$(EXT)
rm -f $(R2USRPLUGDIR)/r2ai$(DOTLIB)
install:
cp -f r2ai.$(EXT) $(R2SYSPLUGDIR)
cp -f r2ai$(DOTLIB) $(R2SYSPLUGDIR)
uninstall:
rm -f $(R2SYSPLUGDIR)/r2ai.$(EXT)
rm -f $(R2SYSPLUGDIR)/r2ai$(DOTLIB)
r2check:
@r2 -qcq --
+179 -17
View File
@@ -34,16 +34,19 @@ make user-install
[0x100003f58]> r2ai -h
Usage: r2ai [-args] [...]
| r2ai -d Decompile current function
| r2ai -d [query] Ask a question on the current function
| r2ai -dr Decompile current function (+ 1 level of recursivity)
| r2ai -a [query] Resolve question using auto mode
| r2ai -e Same as '-e r2ai.'
| 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
| r2ai -M show suggested models for each api
| r2ai -n suggest a better name for the current function
| r2ai -r enter the repl
| r2ai -R ([text]) refresh and query embeddings (see r2ai.data)
| r2ai -r enter the chat repl
| 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
| r2ai -Rq ([text]) refresh and query embeddings (see r2ai.data)
| r2ai -s function signature
| r2ai -x explain current function
| r2ai -v suggest better variables names and types
@@ -52,29 +55,175 @@ Usage: r2ai [-args] [...]
[0x100003f58]>
```
## Configuration
These can be set with `r2ai -e <keyname>=<value>`
| Setting name | Description |
|------------------|------------------------------------------------------------------------------------------------|
| r2ai.api | Name of the provider e.g `openai`. List possibilities with `r2ai -e r2ai.api=?` |
| r2ai.model | Model name. List possibilities with `r2ai -e r2ai.model=?` |
| r2ai.baseurl | Remote LLM base URL. Specify host if necessary e.g http://127.0.0.1:11434. |
| r2ai.max_tokens | Maximum output tokens or maximum total tokens. Check the appropriate limits for your model |
| r2ai.temperature | How creative the model should be. 0=not creative, 1=very creative |
| r2ai.cmds | R2 command to issue and send output in context to model |
| r2ai.lang | Tells LLM which programming language to use for decompilation result. Only works for `r2ai -d` |
| r2ai.hlang | Tells LLM in which language to speak |
| r2ai.prompt | User prompt to send to LLM with `r2ai -d` |
| r2ai.auto.max_runs | Maximum loops when using auto mode `r2ai -a` |
| r2ai.auto.hide_tool_output | Only for auto mode `r2ai -a`. Will not show the output of the tool which ran locally |
| r2ai.auto.yolo | Set this to true if you don't want r2ai to ask you for approval before running commands sent by the LLM. This is **dangerous**. Recommendation: **leave this to false** unless you fully trust your LLM not to create havoc! |
| r2ai.http.use_files | If you get CURL argument too long error, set this to true and arguments will be stored in a file.|
## Example
See [https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
```c
[0x100003f58]> r2ai -e api=claude
[0x100003f58]> r2ai -d
int main(int argc, char **argv, char **envp) {
char buffer[32];
int result = 0;
if (argv[1] != NULL) {
strcpy_chk(buffer, argv[1], 32);
}
return result;
}
[0x100003f58]>
- Get the [substitute example binary](https://github.com/radareorg/r2ai/tree/master/examples)
- Initialize r2 and r2ai
```
$ r2 ./substitute
[0x000010a0]> r2ai -e api=anthropic
[0x000010a0]> r2ai -e model=claude-3-7-sonnet-20250219
[0x000010a0]> aa
```
- Decompile the main function: `r2ai -d`
```
[0x000010a0]> s main
[0x00001303]> r2ai -d
int main(int argc, char **argv, char **envp) {
char input[256];
char encrypted[512];
char *alphabet = "QWERTYUIOPASDFGHJKLZXCVBNM";
printf("Enter text to encrypt: ");
fgets(input, 256, stdin);
input[strcspn(input, "\n")] = 0;
substitute(input, alphabet, encrypted, 0);
printf("Encrypted text: %s\n", encrypted);
substitute(encrypted, alphabet, input, 1);
printf("Decrypted text: %s\n", input);
return 0;
}
```
- Ask a question on the current function: `r2ai -d query`
```
[0x00001303]> r2ai -d what is input buffer size?
Based on the disassembly provided, I can see that the input buffer size is 0x100 (256) bytes.
This is visible in the call to `fgets()` where we see:
```
esi = 0x100
rdi = rax // pointing to var_110h which is the input buffer
sym.imp.fgets() // char *fgets("", 0x00000100, 0)
```
The fgets function is being called with a second argument of 0x100, which specifies the maximum number of characters to read (including the null terminator). This means the program allocates a 256-byte buffer for user input.
```
Note that the context is **reset between each direct query**. For example, the context of the second command `r2ai -d what is input buffer size?` does not contain the request/response of the first `r2ai -d`.
- Find vulnerabilities: `r2ai -V`
```
[0x00001303]> r2ai -V
# Vulnerability Analysis
## Identified Vulnerabilities
1. **Buffer Overflow Risk**: The program uses `fgets()` with a 256-byte buffer to read user input, but doesn't validate if the substitute function's target buffer can handle this much data.
2. **Lack of Input Validation**: There appears to be no validation of the input character range, which could lead to unexpected behavior if special characters are used.
3. **Predictable Encryption**: The cipher uses a hardcoded key "QWERTYUIOPASDFGHJKLZXCVBNM", making it trivially breakable.
4. **Insufficient Memory Handling**: No verification that memory allocations succeed before use.
## Sample Exploit
```python
# Buffer overflow exploit example
from pwn import *
# Connect to the target (adjust as needed)
p = process('./cipher_program')
# Create payload: 256 'A's + return address override
payload = b"A" * 256 + p64(0x4141414141414141) # Example address
# Send the payload
p.sendlineafter(b"Enter text to encrypt: ", payload)
p.interactive()
```
## Security Recommendations
1. **Implement Buffer Size Checks**: Ensure that destination buffers are large enough for the operation and implement bounds checking.
2. **Input Validation**: Validate all user inputs for expected character ranges and length.
3. **Dynamic Key Generation**: Use cryptographically secure random number generators to create unique keys for each session.
4. **Memory Safety**: Use secure memory allocation practices with proper error handling.
5. **Modern Cryptography**: Replace the simple substitution cipher with standard cryptographic libraries (e.g., OpenSSL).
6. **Stack Protection**: Compile with stack canaries (`-fstack-protector-all`) and address space layout randomization (ASLR).
7. **Error Handling**: Implement proper error checking and graceful failure mechanisms.
8. **Secure Coding Practices**: Follow guidelines like OWASP for secure coding standards.
```
- Automatic mode: `r2ai -a query`. In the auto mode, r2ai offers use of tools.
```
[0x00001303]> r2ai -a The main implements a substitution algorithm. Give me an input that would end up as RADARE2 when substituted
[r2cmd]>
[0x00001303]> afl~main
afl~main
0x00001303 1 231 main
[Assistant]
I'll analyze the substitution algorithm in the main function to determine what input would result in "RADARE2" after substitution. Let me examine the main function code.
[r2cmd]>
[0x00001303]> pdf @ main
...
```
- See the chat logs (only in auto mode): `r2ai -L`:
```
[r2ai] Chat Logs (12 messages)
Note: System prompt is applied automatically but not stored in history
[user]: The main implements a substitution algorithm. Give me an input that would end up as RADARE2 when substituted
[assistant]: I'll analyze the substitution algorithm in the main function to determine what input would result in "RADARE2" after substitution. Let me examine the main function code.
[tool call]: r2cmd
{"command":"pdf @ main"}
```
## TODO
* add "undo" command to drop the last message
* dump / restore conversational states (see -L command)
* Implement `~`, `|` and `>` and other r2shell features
* keep context between direct commands unless explicitly reset
* modify context if tool command is user edited
## Messages API
@@ -107,3 +256,16 @@ r2ai_messages_add_tool_call(msgs, "r2cmd", "{\"command\":\"pdf@main\"}", "tool-1
// Free all resources when done
r2ai_messages_free(msgs);
```
## lm-studio
Install [LM Studio](https://lmstudio.ai/). On a server, LM Studio must be run as a normal user (not root) + install *FUSE*.
Then, download your preferred model(s). For example to install GPT-OSS, follow this [cookbook](https://cookbook.openai.com/articles/gpt-oss/run-locally-lmstudio).
When you launch LM Studio, go to developer options, Settings, and select server **port** (1234 by default) and check whether you need "serve on a local network" or not (accessible on localhost only, or on a local network).
In r2ai, as lm-studio uses an OpenAI-like API, configure:
- `r2ai -e api=openai`
- `r2ai -e baseurl=http://LM-STUDIO-IP:PORT`
- `r2ai -e model=?` to list available models
+4 -2
View File
@@ -68,7 +68,7 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
PJ *pj = pj_new ();
pj_o (pj);
pj_ks (pj, "model", modelname (model));
pj_kn (pj, "max_tokens", 4096);
pj_kn (pj, "max_tokens", args.max_tokens);
if (args.thinking_tokens >= 1024) {
pj_ko (pj, "thinking");
pj_ks (pj, "type", "enabled");
@@ -104,7 +104,7 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
// Make the API call
int code = 0;
char *res = r2ai_http_post (anthropic_url, headers, data, &code, NULL);
char *res = r2ai_http_post (core, anthropic_url, headers, data, &code, NULL);
free (data);
free (auth_header);
@@ -316,6 +316,8 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
}
R_IPI char *r2ai_anthropic_stream(RCore *core, R2AIArgs args) {
(void)core;
(void)args;
// Not implemented yet
return NULL;
}
+66 -58
View File
@@ -24,14 +24,14 @@ static R2AIStats stats = { 0 };
static char *format_time_duration(time_t seconds) {
if (seconds < 60) {
return r_str_newf ("%llds", (long long)seconds);
} else if (seconds < 3600) {
return r_str_newf ("%lldm%llds", (long long)(seconds / 60), (long long)(seconds % 60));
} else {
return r_str_newf ("%lldh%lldm%llds",
(long long)(seconds / 3600),
(long long)((seconds % 3600) / 60),
(long long)(seconds % 60));
}
if (seconds < 3600) {
return r_str_newf ("%lldm%llds", (long long)(seconds / 60), (long long)(seconds % 60));
}
return r_str_newf ("%lldh%lldm%llds",
(long long)(seconds / 3600),
(long long)((seconds % 3600) / 60),
(long long)(seconds % 60));
}
// Initialize timing and cost tracking for a run
@@ -54,6 +54,8 @@ static void r2ai_stats_init_run(int n_run) {
// Print a simple run indicator at the start
static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run, int max_runs) {
(void)n_run;
(void)max_runs;
time_t run_time = time (NULL) - stats.start_time;
time_t total_time = time (NULL) - stats.total_start_time;
if (usage) {
@@ -64,16 +66,19 @@ static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run,
stats.total_prompt_tokens += usage->prompt_tokens;
stats.total_completion_tokens += usage->completion_tokens;
}
// TODO: calculate cost
stats.run_cost = 0.0 * run_time;
stats.total_cost += stats.run_cost;
if (r_config_get_b (core->config, "r2ai.chat.show_cost") == true) {
// TODO: calculate cost
stats.run_cost = 0.0 * run_time;
stats.total_cost += stats.run_cost;
}
// Format times for display
char *run_time_str = format_time_duration (run_time);
char *total_time_str = format_time_duration (total_time);
// Print detailed stats
r_cons_printf ("\x1b[1;34m%s | total: %d in: %d out: %d | run: %d in: %d out: %d | %s / %s\x1b[0m\n",
R2_PRINTF ("\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"),
stats.total_tokens,
stats.total_prompt_tokens,
@@ -83,8 +88,8 @@ static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run,
stats.run_completion_tokens,
run_time_str,
total_time_str);
r_cons_newline ();
r_cons_flush ();
R2_NEWLINE ();
R2_FLUSH ();
free (run_time_str);
free (total_time_str);
@@ -115,22 +120,23 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
char *error = NULL;
bool interrupted = false;
const int max_runs = r_config_get_i (core->config, "r2ai.auto.max_runs");
if (n_run > max_runs) {
r_cons_printf ("\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
r_cons_flush ();
R2_PRINTF ("\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
R2_FLUSH ();
return;
}
if (!system_prompt) {
if (R_STR_ISNOTEMPTY (r_config_get (core->config, "r2ai.auto.init_commands"))) {
const char *init_commands = r_config_get (core->config, "r2ai.auto.init_commands");
if (init_commands) {
char *cmd_output = execute_tool (core, "r2cmd", r_str_newf ("{\"command\":\"%s\"}", init_commands));
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, init_commands, cmd_output);
free (cmd_output);
}
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);
free (cmd_output);
}
free (edited_command);
} else {
system_prompt = Gprompt_auto;
}
@@ -165,16 +171,16 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
// Process the response - we need to add it to our messages array
if (message->content || message->reasoning_content) {
r_cons_printf ("\x1b[31m[Assistant]\x1b[0m\n\n");
R2_PRINTF ("\x1b[31m[Assistant]\x1b[0m\n\n");
if (message->reasoning_content) {
r_cons_printf ("\x1b[90m<thinking>\n%s\n</thinking>\x1b[0m\n", message->reasoning_content);
r_cons_newline ();
r_cons_flush ();
R2_PRINTF ("\x1b[90m<thinking>\n%s\n</thinking>\x1b[0m\n", message->reasoning_content);
R2_NEWLINE ();
R2_FLUSH ();
}
if (message->content) {
r_cons_printf ("%s", message->content);
r_cons_newline ();
r_cons_flush ();
R2_PRINTF ("%s", message->content);
R2_NEWLINE ();
R2_FLUSH ();
}
}
@@ -184,7 +190,7 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
// Check for tool calls and process them
if (message->tool_calls && message->n_tool_calls > 0) {
R_LOG_DEBUG ("Found %d tool calls", 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++) {
@@ -195,7 +201,6 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
}
R_LOG_DEBUG ("Tool call: %s", tool_call->name);
// Don't log the full arguments which might get truncated
R_LOG_DEBUG ("Processing tool arguments...");
char *tool_name = strdup (tool_call->name);
char *tool_args = strdup (tool_call->arguments);
if (!tool_name || !tool_args) {
@@ -209,14 +214,17 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
if (interrupted) {
cmd_output = strdup ("<user interrupted>");
} else {
cmd_output = execute_tool (core, tool_name, tool_args);
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
free (edited_command);
}
free (tool_name);
free (tool_args);
if (strcmp (cmd_output, "R2AI_SIGINT") == 0) {
r_cons_printf ("\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
r_cons_flush ();
R2_PRINTF ("\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
R2_FLUSH ();
free (cmd_output);
cmd_output = strdup ("<user interrupted>");
interrupted = true;
@@ -273,27 +281,27 @@ R_IPI void cmd_r2ai_a(RCore *core, const char *user_query) {
}
// Helper function to display content with length indication for long content
static void print_content_with_length(const char *content, const char *empty_msg, bool always_show_length) {
static void print_content_with_length(RCore *core, const char *content, const char *empty_msg, bool always_show_length) {
if (!content || *content == '\0') {
r_cons_printf ("%s\n", empty_msg ? empty_msg : "<no content>");
R2_PRINTF ("%s\n", empty_msg ? empty_msg : "<no content>");
return;
}
size_t content_len = strlen (content);
const int max_display = 200;
const size_t max_display = 200;
if (content_len > max_display) {
// Truncate long content and show length
char *truncated = r_str_ndup (content, max_display);
r_cons_printf ("%s... \x1b[1;37m(length: %zu chars)\x1b[0m\n",
R2_PRINTF ("%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)
r_cons_printf ("%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
R2_PRINTF ("%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
content, content_len);
} else {
r_cons_printf ("%s\n", content);
R2_PRINTF ("%s\n", content);
}
}
@@ -302,7 +310,7 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
// Get conversation
R2AI_Messages *messages = r2ai_conversation_get ();
if (!messages || messages->n_messages == 0) {
r_cons_printf ("No conversation history available\n");
R2_PRINTF ("No conversation history available\n");
return;
}
@@ -353,16 +361,16 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
pj_end (pj);
char *json_str = pj_drain (pj);
r_cons_printf ("%s\n", json_str);
R2_PRINTF ("%s\n", json_str);
free (json_str);
return;
}
r_cons_printf ("\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
messages->n_messages);
R2_PRINTF ("\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
core->cons, messages->n_messages);
r_cons_printf ("\x1b[1;33mNote: System prompt is applied automatically but not stored in history\x1b[0m\n\n");
R2_PRINTF ("\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 < messages->n_messages; i++) {
@@ -371,35 +379,35 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
// Format based on role
if (!strcmp (role, "user")) {
r_cons_printf ("\x1b[1;32m[user]:\x1b[0m ");
print_content_with_length (msg->content, "<no content>", false);
R2_PRINTF ("\x1b[1;32m[user]:\x1b[0m ");
print_content_with_length (core, msg->content, "<no content>", false);
} else if (!strcmp (role, "assistant")) {
r_cons_printf ("\x1b[1;36m[assistant]:\x1b[0m ");
print_content_with_length (msg->content, "<no content>", false);
R2_PRINTF ("\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];
r_cons_printf (" \x1b[1;35m[tool call]:\x1b[0m %s\n",
R2_PRINTF (" \x1b[1;35m[tool call]:\x1b[0m %s\n",
tc->name ? tc->name : "<unnamed>");
if (tc->arguments) {
r_cons_printf (" %s\n", tc->arguments);
R2_PRINTF (" %s\n", tc->arguments);
}
}
}
} else if (!strcmp (role, "tool")) {
r_cons_printf ("\x1b[1;35m[tool]:\x1b[0m ");
print_content_with_length (msg->content, "<no result>", true);
R2_PRINTF ("\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.)
r_cons_printf ("\x1b[1;37m[%s]:\x1b[0m ", role);
print_content_with_length (msg->content, "<no content>", false);
R2_PRINTF ("\x1b[1;37m[%s]:\x1b[0m ", role);
print_content_with_length (core, msg->content, "<no content>", false);
}
r_cons_newline ();
r_cons_flush ();
R2_NEWLINE ();
R2_FLUSH ();
}
}
+31
View File
@@ -0,0 +1,31 @@
/* Copyright r2ai - 2023-2025 - pancake */
#include <r_core.h>
#include "r2ai.h"
int main(int argc, char **argv) {
if (argc < 2) {
eprintf ("Usage: r2ai <prompt>\n");
return 1;
}
const char *prompt = argv[1];
RCore *core = r_core_new ();
char *err = NULL;
R2AIArgs args = {
.input = prompt,
.error = &err,
.dorag = false,
};
char *res = r2ai (core, args);
if (res) {
R_LOG_INFO (res);
free (res);
}
free (err);
r_core_free (core);
return 0;
}
+216
View File
@@ -0,0 +1,216 @@
project('r2ai', 'c',
version : '0.1.0',
default_options : ['warning_level=2', 'c_std=c11']
)
cc = meson.get_compiler('c')
# radare2 core dependency: use pkg-config when available. Do not fall back
# to CMake-based dependency discovery; instead, if pkg-config fails, try
# locating the library under user-provided prefixes (see -Dr2_prefix).
r_core_dep = dependency('r_core', method: 'pkg-config', required: false)
# Centralized resolution results: when we locate `r_core` manually below we
# record the library and include search directories in `r2_libdirs` and
# `r2_incdirs`. Later lookups for other `r_*` libraries will assume they
# live in the same locations, avoiding repeating complex prefix search
# logic.
r2_libdirs = []
r2_incdirs = []
# If pkg-config didn't yield an r_core dependency, fall back to manual
# discovery logic below (search common prefixes / extracted zips).
if not r_core_dep.found()
# On Windows prefer explicit discovery like other third_party projects do:
# 1) ask an installed `radare2` binary for its lib/include/plugin dirs
# (via `radare2 -H R2_LIBDIR` / `R2_INCDIR`),
# 2) fall back to -Dr2_prefix provided by CI, and
# 3) try common locations such as C:\\radare2\\lib and <prefix>/bin.
# This avoids Meson's CMake-based dependency fallback which doesn't work
# reliably with radare2 release zips on Windows.
is_windows_like = host_machine.system() == 'windows' or cc.get_id() == 'msvc' or cc.get_id() == 'clang-cl'
# Simpler, robust Windows behavior mirroring third_party projects:
# Try to find r_core by checking a small list of likely library dirs.
if is_windows_like
r2libdirs = []
# prefer explicit prefix if given
if get_option('r2_prefix') != ''
p = get_option('r2_prefix')
r2libdirs += [ join_paths(p, 'lib'), join_paths(p, 'radare2', 'lib') ]
endif
# repository-local radare2 (used by some subprojects) and conventional path
r2libdirs += [ meson.current_source_dir() + '/radare2/lib', 'C:/radare2/lib']
# try to find r_core in the candidate dirs (MSVC import libs or mingw import libs)
rcore_lib = cc.find_library('r_core', dirs: r2libdirs, required: false)
if not rcore_lib.found()
rcore_lib = cc.find_library('libr_core', dirs: r2libdirs, required: false)
endif
if rcore_lib.found()
# basic include candidates
incdirs = []
if get_option('r2_prefix') != ''
p = get_option('r2_prefix')
incdirs += [ join_paths(p, 'include', 'libr'), join_paths(p, 'include') ]
endif
rcore_inc = include_directories(incdirs)
r_core_dep = declare_dependency(dependencies: [rcore_lib], include_directories: rcore_inc)
# Remember resolved dirs for other r_* lookups
r2_libdirs = r2libdirs
r2_incdirs = incdirs
else
error('Windows dependency lookup for r_core failed: ensure radare2 is installed and pass -Dr2_prefix=<prefix> (e.g. C:/radare2)')
endif
else
# Non-Windows: try locating radare2 from environment-provided prefixes. This
# helps CI on Unix where pkg-config may not be installed.
r2_prefixes = []
if get_option('r2_prefix') != ''
r2_prefixes += [get_option('r2_prefix')]
endif
found = false
foreach p : r2_prefixes
# Common library/include locations under a provided prefix.
libdirs = [
join_paths(p, 'lib'),
join_paths(p, 'lib64'),
join_paths(p, 'radare2', 'lib'),
join_paths(p, 'radare2', 'lib64'),
join_paths(p, 'mingw64', 'lib'),
]
incdirs = [
join_paths(p, 'include'),
join_paths(p, 'radare2', 'include'),
join_paths(p, 'include', 'radare2'),
]
extra_libdirs = libdirs + [ join_paths(p, 'bin') ]
candidate_names = [ 'r_core', 'r_core-6', 'r_core6', 'r_core64', 'libr_core' ]
foreach name : candidate_names
rcore_lib = cc.find_library(name, dirs: extra_libdirs, required: false)
if rcore_lib.found()
rcore_inc = include_directories(incdirs + [ join_paths(p, 'include', 'libr') ])
r_core_dep = declare_dependency(dependencies: [rcore_lib], include_directories: rcore_inc)
# Record the successful prefix and candidate lib/include dirs so
# other r_* dependencies can be looked up in the same locations.
r2_libdirs = extra_libdirs
r2_incdirs = incdirs + [ join_paths(p, 'include', 'libr') ]
found = true
break
endif
endforeach
if found
break
endif
endforeach
if not found
error('Dependency lookup for r_core failed: install radare2 development files or provide pkg-config paths or set the meson option -Dr2_prefix=<prefix>')
endif
endif
endif
# Resolve other r_* dependencies assuming same paths as r_core
r_util_dep = dependency('r_util', method: 'pkg-config', required: false)
if not r_util_dep.found() and r2_libdirs.length() > 0
foreach name : ['r_util', 'libr_util']
ulib = cc.find_library(name, dirs: r2_libdirs, required: false)
if ulib.found()
uinc = include_directories(r2_incdirs)
r_util_dep = declare_dependency(dependencies: [ulib], include_directories: uinc)
break
endif
endforeach
endif
if not r_util_dep.found()
warning('r_util dependency not found via pkg-config or same paths as r_core; build may fail on systems without radare2 dev files')
endif
r_cons_dep = dependency('r_cons', method: 'pkg-config', required: false)
if not r_cons_dep.found() and r2_libdirs.length() > 0
foreach name : ['r_cons', 'libr_cons']
clib = cc.find_library(name, dirs: r2_libdirs, required: false)
if clib.found()
cinc = include_directories(r2_incdirs)
r_cons_dep = declare_dependency(dependencies: [clib], include_directories: cinc)
break
endif
endforeach
endif
if not r_cons_dep.found()
warning('r_cons dependency not found via pkg-config or same paths as r_core; console helpers may be unavailable')
endif
r_config_dep = dependency('r_config', method: 'pkg-config', required: false)
if not r_config_dep.found() and r2_libdirs.length() > 0
foreach name : ['r_config', 'libr_config']
clib = cc.find_library(name, dirs: r2_libdirs, required: false)
if clib.found()
cinc = include_directories(r2_incdirs)
r_config_dep = declare_dependency(dependencies: [clib], include_directories: cinc)
break
endif
endforeach
endif
if not r_config_dep.found()
warning('r_config dependency not found via pkg-config or same paths as r_core; console helpers may be unavailable')
endif
r_socket_dep = dependency('r_socket', method: 'pkg-config', required: false)
if not r_socket_dep.found() and r2_libdirs.length() > 0
foreach name : ['r_socket', 'libr_socket']
slib = cc.find_library(name, dirs: r2_libdirs, required: false)
if slib.found()
sinc = include_directories(r2_incdirs)
r_socket_dep = declare_dependency(dependencies: [slib], include_directories: sinc)
break
endif
endforeach
endif
if not r_socket_dep.found()
warning('r_socket dependency not found via pkg-config or same paths as r_core; socket helpers may be unavailable')
endif
# Math library dependency (needed for log, sqrt, etc.)
m_dep = cc.find_library('m', required: false)
# Common compile flags similar to the Makefile
c_args = []
if cc.get_id() == 'gcc' or cc.get_id() == 'clang'
c_args += ['-Wall', '-g', '-fPIC']
elif cc.get_id() == 'msvc'
c_args += ['/Zi']
endif
srcs = files(
'r2ai.c',
'auto.c',
'vdb.c',
'tools.c',
'messages.c',
'anthropic.c',
'r2ai_http.c',
'openai.c',
'markdown.c',
)
# Shared library (plugin)
shared_lib = shared_library(
'r2ai',
srcs,
install: true,
c_args: c_args,
dependencies: [r_core_dep, r_util_dep, r_cons_dep, r_config_dep, r_socket_dep, m_dep]
)
# Command-line executable (links the same objects plus main.c)
exe = executable(
'r2ai',
[ 'main.c' ],
link_with: shared_lib,
install: true,
c_args: c_args,
dependencies: [r_core_dep, r_util_dep, r_cons_dep, r_config_dep, r_socket_dep, m_dep]
)
+6
View File
@@ -0,0 +1,6 @@
option('r2_prefix',
type: 'string',
value: '',
description: 'Path prefix where radare2 is installed (include/ and lib/).'
)
+17 -32
View File
@@ -2,6 +2,7 @@
#include "r2ai.h"
// AITODO: remove globals
// Static global message store for session persistence
static R2AI_Messages *conversation = NULL;
@@ -13,10 +14,10 @@ R_API void r2ai_message_free(R2AI_Message *msg) {
return;
}
free ((void *)msg->role);
free ((void *)msg->content);
free ((void *)msg->reasoning_content);
free ((void *)msg->tool_call_id);
free (msg->role);
free (msg->content);
free (msg->reasoning_content);
free (msg->tool_call_id);
// Free tool calls
if (msg->tool_calls) {
@@ -31,21 +32,18 @@ R_API void r2ai_message_free(R2AI_Message *msg) {
if (msg->content_blocks) {
for (int i = 0; i < msg->content_blocks->n_blocks; i++) {
R2AI_ContentBlock *block = &msg->content_blocks->blocks[i];
free ((void *)block->type);
free ((void *)block->data);
free ((void *)block->thinking);
free ((void *)block->signature);
free ((void *)block->text);
free ((void *)block->id);
free ((void *)block->name);
free ((void *)block->input);
free (block->type);
free (block->data);
free (block->thinking);
free (block->signature);
free (block->text);
free (block->id);
free (block->name);
free (block->input);
}
free ((void *)msg->content_blocks->blocks);
free ((void *)msg->content_blocks);
free (msg->content_blocks->blocks);
free (msg->content_blocks);
}
// Clear the struct but don't free it
memset (msg, 0, sizeof (R2AI_Message));
}
// Initialize conversation container (call this during plugin init)
@@ -56,9 +54,6 @@ R_API void r2ai_conversation_init(void) {
}
conversation = R_NEW0 (R2AI_Messages);
if (!conversation) {
return;
}
conversation->cap_messages = INITIAL_CAPACITY;
conversation->messages = R_NEWS0 (R2AI_Message, conversation->cap_messages);
if (!conversation->messages) {
@@ -564,7 +559,7 @@ R_API char *r2ai_msgs_to_anthropic_json(const R2AI_Messages *msgs) {
pj_ko (pj, "input"); // Start input object
if (arguments) {
for (int k = 0; k < arguments->children.count; k++) {
for (size_t k = 0; k < arguments->children.count; k++) {
const RJson *arg = r_json_item (arguments, k);
if (arg && arg->type == R_JSON_STRING) {
pj_ks (pj, arg->key, arg->str_value);
@@ -647,7 +642,6 @@ R_API PJ *r_json_to_pj(const RJson *json, PJ *existing_pj) {
if (!existing_pj) {
pj_o (pj);
}
// Handle object's properties
const RJson *prop = json->children.first;
while (prop) {
@@ -694,17 +688,14 @@ R_API PJ *r_json_to_pj(const RJson *json, PJ *existing_pj) {
}
prop = prop->next;
}
if (!existing_pj) {
pj_end (pj);
}
break;
case R_JSON_ARRAY:
if (!existing_pj) {
pj_a (pj);
}
// Handle array items
const RJson *item = json->children.first;
while (item) {
@@ -749,12 +740,10 @@ R_API PJ *r_json_to_pj(const RJson *json, PJ *existing_pj) {
}
item = item->next;
}
if (!existing_pj) {
pj_end (pj);
}
break;
default:
if (!existing_pj) {
pj_free (pj);
@@ -768,11 +757,7 @@ R_API PJ *r_json_to_pj(const RJson *json, PJ *existing_pj) {
// Helper function to clone RJson to string
R_API char *r_json_to_string(const RJson *json) {
PJ *pj = r_json_to_pj (json, NULL);
if (!pj) {
return NULL;
}
char *result = pj_drain (pj);
return result;
return pj? pj_drain (pj): NULL;
}
// Create a conversation with optional initial user message
+33 -32
View File
@@ -23,6 +23,7 @@ typedef struct {
// Hash table to store model compatibility info
static HtPP *model_compat_db = NULL;
#if 0
// Function to check if a model has a specific error flag
static bool model_has_error(const char *provider, const char *model, ModelErrorFlags flag) {
if (!model_compat_db) {
@@ -30,9 +31,8 @@ static bool model_has_error(const char *provider, const char *model, ModelErrorF
}
char *key = r_str_newf ("%s:%s", provider, model ? model : "default");
ModelCompat *compat = NULL;
bool found_flag = false;
compat = ht_pp_find (model_compat_db, key, &found_flag);
ModelCompat *compat = ht_pp_find (model_compat_db, key, &found_flag);
free (key);
if (found_flag && compat) {
@@ -40,6 +40,7 @@ static bool model_has_error(const char *provider, const char *model, ModelErrorF
}
return false;
}
#endif
// Function to add an error flag to a model
static void model_add_error(const char *provider, const char *model, ModelErrorFlags flag) {
@@ -66,6 +67,8 @@ static void model_add_error(const char *provider, const char *model, ModelErrorF
// Free a ModelCompat item (for hash table)
static bool model_compat_free_cb(void *user, const void *k, const void *v) {
(void)user;
(void)k;
ModelCompat *compat = (ModelCompat *)v;
if (compat) {
free (compat->model_id);
@@ -84,34 +87,12 @@ R_IPI void r2ai_openai_fini(void) {
}
R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
const char *base_url = r_config_get (core->config, "r2ai.base_url");
// Initialize compatibility database if needed
if (!model_compat_db) {
model_compat_db = ht_pp_new0 ();
}
if (R_STR_ISEMPTY (base_url)) {
if (strcmp (args.provider, "openai") == 0) {
base_url = "https://api.openai.com/v1";
} else if (strcmp (args.provider, "gemini") == 0) {
base_url = "https://generativelanguage.googleapis.com/v1beta/openai";
} else if (strcmp (args.provider, "ollama") == 0) {
base_url = "http://localhost:11434/v1";
} else if (strcmp (args.provider, "xai") == 0) {
base_url = "https://api.x.ai/v1";
} else if (strcmp (args.provider, "anthropic") == 0) { // also "claude" ?
base_url = "https://api.anthropic.com/v1";
} else if (strcmp (args.provider, "openapi") == 0) {
base_url = "http://127.0.0.1:11434";
} else if (strcmp (args.provider, "openrouter") == 0) {
base_url = "https://openrouter.ai/api/v1";
} else if (strcmp (args.provider, "groq") == 0) { // isnt the same as x.ai?
base_url = "https://api.groq.com/openai/v1";
} else if (strcmp (args.provider, "mistral") == 0) {
base_url = "https://api.mistral.ai/v1";
}
}
const char *base_url = r2ai_get_provider_url (core, args.provider);
// TODO: default model name should depend on api
const char *model_name = args.model ? args.model : "gpt-4o-mini";
char **error = args.error;
@@ -169,10 +150,24 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
*error = NULL;
}
char *auth_header = r_str_newf ("Authorization: Bearer %s", args.api_key);
R_LOG_DEBUG ("Auth header: %s", auth_header);
const char *headers[] = { "Content-Type: application/json", auth_header, NULL };
const char *openai_url = r_str_newf ("%s/chat/completions", base_url);
const char **headers = NULL;
char *auth_header = NULL;
if (R_STR_ISNOTEMPTY (args.api_key)) {
auth_header = r_str_newf ("Authorization: Bearer %s", args.api_key);
R_LOG_DEBUG ("Auth header: %s", auth_header);
static const char *static_headers[] = { NULL, NULL, NULL };
headers = static_headers;
headers[0] = "Content-Type: application/json";
headers[1] = auth_header;
}
const char *openai_url;
if (strcmp (args.provider, "ollama") == 0) {
openai_url = r_str_newf ("%s/chat", base_url);
} else {
openai_url = r_str_newf ("%s/chat/completions", base_url);
}
R_LOG_DEBUG ("OpenAI URL: %s", openai_url);
// Create a messages JSON object, either from input messages or from content
char *messages_json = NULL;
@@ -207,10 +202,14 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
pj_ks (pj, "model", model_name);
pj_kb (pj, "stream", false);
#if 0
// gpt-5-mini-chat is the only gpt-5 model that supports temperature
// gpt-5 gpt-5-mini and gpt-5-nano just throw an error
// Only add temperature if this provider/model doesn't have the temperature error flag
if (!model_has_error (args.provider, model_name, MODEL_ERROR_TEMPERATURE)) {
pj_kd (pj, "temperature", args.temperature ? args.temperature : 0.01);
}
#endif
if (strcmp (args.provider, "mistral") == 0) {
pj_kn (pj, "max_tokens", args.max_tokens ? args.max_tokens : 5128);
@@ -273,7 +272,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
// Make the API call
char *res = NULL;
int code = 0;
res = r2ai_http_post (openai_url, headers, complete_json, &code, NULL);
res = r2ai_http_post (core, openai_url, headers, complete_json, &code, NULL);
free (complete_json);
if (code != 200) {
@@ -282,7 +281,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, 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-4o-mini";
const char *model_name = args.model ? args.model : "gpt-5-mini";
// Check for temperature errors
if (strstr (res, "temperature")) {
@@ -371,7 +370,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
R2AI_ToolCall *tool_calls_array = R_NEWS0 (R2AI_ToolCall, n_tool_calls);
if (tool_calls_array) {
for (size_t i = 0; i < n_tool_calls; i++) {
for (int i = 0; i < n_tool_calls; i++) {
const RJson *tool_call = r_json_item (tool_calls, i);
if (!tool_call) {
continue;
@@ -424,5 +423,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
}
R_IPI char *r2ai_openai_stream(RCore *core, R2AIArgs args) {
(void)core;
(void)args;
return NULL;
}
+320 -208
View File
@@ -49,110 +49,56 @@ static void refresh_embeddings(RCore *core) {
static RCoreHelpMessage help_msg_r2ai = {
"Usage:",
"r2ai",
" [-args] [...]",
"r2ai",
" -d",
"Decompile current function",
"r2ai",
" -dr",
"Decompile current function (+ 1 level of recursivity)",
"r2ai",
" -a [query]",
"Resolve question using auto mode",
"r2ai",
" -e",
"Same as '-e r2ai.'",
"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",
"r2ai",
" -n",
"suggest a better name for the current function",
"r2ai",
" -r",
"enter the chat repl",
"r2ai",
" -L",
"show chat logs (See -Lj for json)",
"r2ai",
" -L-[N]",
"delete the last (or N last messages from the chat history)",
"r2ai",
" -R",
"reset the chat conversation context",
"r2ai",
" -Rq ([text])",
"refresh and query embeddings (see r2ai.data)",
"r2ai",
" -s",
"function signature",
"r2ai",
" -x",
"explain current function",
"r2ai",
" -v",
"suggest better variables names and types",
"r2ai",
" -V[r]",
"find vulnerabilities in the decompiled code (-Vr uses -dr)",
"r2ai",
" [arg]",
"send a post request to talk to r2ai and print the output",
"r2ai", " [-args] [...]",
"r2ai", " -d", "Decompile current function",
"r2ai", " -d [query]", "Ask a question on the current function",
"r2ai", " -dr", "Decompile current function (+ 1 level of recursivity)",
"r2ai", " -a [query]", "Resolve question using auto mode",
"r2ai", " -e", "Same as '-e r2ai.'",
"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",
"r2ai", " -n", "suggest a better name for the current function",
"r2ai", " -r", "enter the chat repl",
"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",
"r2ai", " -Rq ([text])", "refresh and query embeddings (see r2ai.data)",
"r2ai", " -s", "function signature",
"r2ai", " -x", "explain current function",
"r2ai", " -v", "suggest better variables names and types",
"r2ai", " -V[r]", "find vulnerabilities in the decompiled code (-Vr uses -dr)",
"r2ai", " [arg]", "send a post request to talk to r2ai and print the output",
NULL
};
#if 0
// TODO: use it for r2ai.data.reason
static char *vdb_from(RCore *core, const char *prompt) {
char *q = r_str_newf (
"# Instruction\n"
"Deconstruct the prompt and respond ONLY with the list of multiple prompts necessary to resolve it\n"
"## Prompt\n%s\n",
prompt);
char *error = NULL;
R2AIArgs vdb_args = {
.input = q,
.error = &error,
.dorag = false
};
char *r = r2ai (core, vdb_args);
if (error) {
free (q);
free (r);
return NULL;
/* Return a malloc'd API key read from the environment or from ~/.r2ai.<provider>-key
* Caller is responsible for freeing the returned string (or NULL if not found). */
static char *r2ai_get_api_key(RCore *core, const char *provider) {
(void)core;
char *api_key = NULL;
char *api_key_env = r_str_newf ("%s_API_KEY", provider);
r_str_case (api_key_env, true);
char *s = r_sys_getenv (api_key_env);
free (api_key_env);
if (R_STR_ISNOTEMPTY (s)) {
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);
}
}
free (api_key_filename);
free (absolute_apikey);
}
return r;
return api_key;
}
static char *rag(RCore *core, const char *content, const char *prompt) {
char *q = r_str_newf (
"# Instruction\n"
"Filter the statements. Respond ONLY the subset of statements matching the prompt. Do not introduce the output. Do not use markdown\n"
"## Prompt\n%s\n"
"## Statements\n%s\n",
prompt, content);
char *error = NULL;
R2AIArgs rag_args = {
.input = q,
.error = &error,
.dorag = false
};
char *r = r2ai (core, rag_args);
if (error) {
free (q);
free (r);
return NULL;
}
return r;
}
#endif
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
R2AI_ChatResponse *res = NULL;
@@ -161,7 +107,8 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
provider = "gemini";
}
if (!args.model) {
args.model = strdup (r_config_get (core->config, "r2ai.model"));
const char *config_model = r_config_get (core->config, "r2ai.model");
args.model = strdup (config_model? config_model: "");
}
args.provider = strdup (provider);
@@ -169,39 +116,26 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
args.max_tokens = r_config_get_i (core->config, "r2ai.max_tokens");
}
if (!args.temperature) {
args.temperature = atof (r_config_get (core->config, "r2ai.temperature"));
const char *configtemp = r_config_get (core->config, "r2ai.temperature");
args.temperature = configtemp? atof (configtemp): 0;
}
const char *api_key_env = r_str_newf ("%s_API_KEY", provider);
char *api_key_env_copy = strdup (api_key_env);
r_str_case (api_key_env_copy, true);
const char *api_key_filename = r_str_newf ("~/.r2ai.%s-key", provider);
char *api_key = NULL;
char *s = r_sys_getenv (api_key_env_copy);
if (R_STR_ISNOTEMPTY (s)) {
api_key = s;
} else {
free (s);
char *absolute_apikey = r_file_abspath (api_key_filename);
if (r_file_exists (absolute_apikey)) {
api_key = r_file_slurp (absolute_apikey, NULL);
if (strcmp (provider, "ollama") && strcmp (provider, "openapi")) {
api_key = r2ai_get_api_key (core, provider);
if (api_key) {
args.api_key = api_key;
}
free (absolute_apikey);
}
free (api_key_env_copy);
if (api_key) {
r_str_trim (api_key);
args.api_key = api_key;
}
// Make sure we have an API key before proceeding
if (strcmp (provider, "ollama")) {
if (R_STR_ISEMPTY (args.api_key)) {
char *Provider = strdup (provider);
r_str_case (Provider, true);
R_LOG_ERROR ("No API key found for %s provider. Please set one with: r2ai "
"-e %s.api_key=YOUR_KEY",
provider, provider);
"%s_API_KEY=YOUR_KEY",
provider, Provider);
free (Provider);
return NULL;
}
}
@@ -270,7 +204,7 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
return res;
}
R_IPI char *r2ai(RCore *core, R2AIArgs args) {
R_API char *r2ai(RCore *core, R2AIArgs args) {
if (R_STR_ISEMPTY (args.input) && !args.messages) {
if (args.error) {
*args.error = r_str_newf ("Usage: r2ai [-h] [prompt]");
@@ -322,11 +256,16 @@ static void cmd_r2ai_d(RCore *core, const char *input, const bool recursive) {
const char *prompt = r_config_get (core->config, "r2ai.prompt");
const char *lang = r_config_get (core->config, "r2ai.lang");
char *full_prompt;
if (!R_STR_ISEMPTY (lang)) {
full_prompt = r_str_newf (
"%s. Translate the code into %s programming language.", prompt, lang);
if (!R_STR_ISEMPTY (input)) {
R_LOG_DEBUG ("User question: %s", input);
full_prompt = strdup (input);
} else {
full_prompt = strdup (prompt);
if (!R_STR_ISEMPTY (lang)) {
full_prompt = r_str_newf (
"%s. Translate the code into %s programming language.", prompt, lang);
} else {
full_prompt = strdup (prompt);
}
}
char *cmds = strdup (r_config_get (core->config, "r2ai.cmds"));
RStrBuf *sb = r_strbuf_new (full_prompt);
@@ -371,7 +310,7 @@ static void cmd_r2ai_d(RCore *core, const char *input, const bool recursive) {
R_LOG_ERROR (error);
free (error);
} else {
r_cons_printf ("%s\n", res);
R2_PRINTF ("%s\n", res);
}
free (res);
r_list_free (cmdslist);
@@ -421,9 +360,9 @@ static void cmd_r2ai_repl(RCore *core) {
}
if (*ptr == '/') {
if (ptr[1] == '?' || r_str_startswith (ptr, "/help")) {
r_cons_println ("/help show this help");
r_cons_println ("/reset reset conversation");
r_cons_println ("/quit same as ^D, leave the repl");
R2_PRINTLN ("/help show this help");
R2_PRINTLN ("/reset reset conversation");
R2_PRINTLN ("/quit same as ^D, leave the repl");
} else if (r_str_startswith (ptr, "/reset")) {
r_strbuf_free (sb);
sb = r_strbuf_new ("");
@@ -442,8 +381,8 @@ static void cmd_r2ai_repl(RCore *core) {
free (error);
} else if (res) {
r_strbuf_appendf (sb, "Assistant: %s\n", res);
r_cons_println (res);
r_cons_flush ();
R2_PRINTF ("%s\n", res);
R2_FLUSH ();
}
free (res);
}
@@ -501,7 +440,7 @@ static void cmd_r2ai_n(RCore *core) {
R_LOG_ERROR (error);
free (error);
} else {
r_cons_printf ("%s\n", res);
R2_PRINTF ("%s\n", res);
}
free (res);
free (q);
@@ -528,7 +467,7 @@ static void cmd_r2ai_i(RCore *core, const char *arg) {
R_LOG_ERROR (error);
free (error);
} else {
r_cons_printf ("%s\n", res);
R2_PRINTF ("%s\n", res);
}
free (fname);
free (res);
@@ -569,7 +508,7 @@ static void cmd_r2ai_s(RCore *core) {
*end = 0;
}
r_str_trim (nl);
r_cons_printf ("'afs %s\n", nl);
R2_PRINTF ("'afs %s\n", nl);
}
}
}
@@ -594,7 +533,7 @@ static void cmd_r2ai_v(RCore *core) {
R_LOG_ERROR (error);
free (error);
} else {
r_cons_println (res);
R2_PRINTLN (res);
}
free (afv);
free (res);
@@ -615,7 +554,7 @@ static void cmd_r2ai_V(RCore *core, bool recursive) {
R_LOG_ERROR (error);
free (error);
} else {
r_cons_printf ("%s\n", res);
R2_PRINTF ("%s\n", res);
}
free (res);
free (q);
@@ -624,13 +563,13 @@ static void cmd_r2ai_V(RCore *core, bool recursive) {
static void cmd_r2ai_m(RCore *core, const char *input) {
if (R_STR_ISEMPTY (input)) {
r_cons_printf ("%s\n", r_config_get (core->config, "r2ai.model"));
R2_PRINTF ("%s\n", r_config_get (core->config, "r2ai.model"));
return;
}
r_config_lock (core->config, false);
r_config_set (core->config, "r2ai.model", input);
r_config_lock (core->config, true);
r_cons_printf ("Model set to %s\n", input);
R2_PRINTF ("Model set to %s\n", input);
}
static void load_embeddings(RCore *core, RVdb *db) {
@@ -683,10 +622,10 @@ static void cmd_r2ai(RCore *core, const char *input) {
const int N = atoi (arg);
R2AI_Messages *messages = r2ai_conversation_get ();
if (!messages || messages->n_messages == 0) {
r_cons_printf ("No conversation history available\n");
R2_PRINTF ("No conversation history available\n");
} else {
r2ai_delete_last_messages (messages, N);
r_cons_printf ("Deleted %d message%s from chat history\n", N > 0 ? N : 1,
R2_PRINTF ("Deleted %d message%s from chat history\n", N > 0 ? N : 1,
(N > 0 && N != 1) ? "s" : "");
}
} else if (r_str_startswith (input, "-L")) {
@@ -713,7 +652,7 @@ static void cmd_r2ai(RCore *core, const char *input) {
for (i = 0; i < rs->size; i++) {
RVdbResult *r = &rs->results[i];
KDNode *n = r->node;
r_cons_printf ("- (%.4f) %s\n", r->dist_sq, n->text);
R2_PRINTF ("- (%.4f) %s\n", r->dist_sq, n->text);
}
r_vdb_result_free (rs);
}
@@ -730,21 +669,13 @@ static void cmd_r2ai(RCore *core, const char *input) {
} else if (r_str_startswith (input, "-r")) {
cmd_r2ai_repl (core);
} else if (r_str_startswith (input, "-R")) {
#if 0
if (strlen (input) <= 2 || isspace (input[2])) {
R2AI_Messages *messages = r2ai_conversation_get ();
if (!messages || messages->n_messages == 0) {
r_cons_printf ("No conversation history to reset\n");
} else {
r2ai_msgs_clear (messages);
r_cons_printf ("Chat conversation context has been reset\n");
}
R2AI_Messages *messages = r2ai_conversation_get ();
if (!messages || messages->n_messages == 0) {
R2_PRINTF ("No conversation history to reset\n");
} else {
cmd_r2ai_R (core, r_str_trim_head_ro (input + 2));
r2ai_msgs_clear (messages);
R2_PRINTF ("Chat conversation context has been reset\n");
}
#else
cmd_r2ai_R (core, r_str_trim_head_ro (input + 2));
#endif
} else if (r_str_startswith (input, "-Rq")) {
cmd_r2ai_R (core, r_str_trim_head_ro (input + 3));
} else if (r_str_startswith (input, "-m")) {
@@ -760,24 +691,158 @@ static void cmd_r2ai(RCore *core, const char *input) {
R_FREE (err);
}
if (res) {
r_cons_printf ("%s\n", res);
R2_PRINTF ("%s\n", res);
free (res);
}
}
}
R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider) {
if (strcmp (provider, "openai") == 0) {
const char *host = r_config_get (core->config, "r2ai.baseurl");
if (R_STR_ISNOTEMPTY (host)) {
if (r_str_startswith (host, "http")) {
return r_str_newf ("%s/v1", host);
}
return r_str_newf ("http://%s/v1", host);
}
return "https://api.openai.com/v1";
} else if (strcmp (provider, "gemini") == 0) {
return "https://generativelanguage.googleapis.com/v1beta/openai";
} else if (strcmp (provider, "ollama") == 0) {
const char *host = r_config_get (core->config, "r2ai.baseurl");
if (R_STR_ISNOTEMPTY (host)) {
if (r_str_startswith (host, "http")) {
return r_str_newf ("%s/api", host);
}
return r_str_newf ("http://%s/api", host);
}
return "http://localhost:11434/api";
} else if (strcmp (provider, "xai") == 0) {
return "https://api.x.ai/v1";
} else if (strcmp (provider, "anthropic") == 0) {
return "https://api.anthropic.com/v1";
} else if (strcmp (provider, "openapi") == 0) {
return "http://127.0.0.1:11434";
} else if (strcmp (provider, "openrouter") == 0) {
return "https://openrouter.ai/api/v1";
} else if (strcmp (provider, "groq") == 0) {
return "https://api.groq.com/openai/v1";
} else if (strcmp (provider, "mistral") == 0) {
return "https://api.mistral.ai/v1";
}
return NULL;
}
static RList *fetch_available_models(RCore *core, const char *provider) {
const char *purl = r2ai_get_provider_url (core, provider);
if (!purl) {
return NULL;
}
// Create models endpoint URL
char *models_url = NULL;
if (strcmp (provider, "ollama") != 0) {
models_url = r_str_newf ("%s/models", purl);
} else {
models_url = r_str_newf ("%s/tags", purl);
}
if (!models_url) {
return NULL;
}
// Get API key for authentication (except for ollama and openapi)
char *api_key = NULL;
if (strcmp (provider, "ollama") && strcmp (provider, "openapi")) {
// Consolidated helper to fetch the API key from env or file
api_key = r2ai_get_api_key (core, provider);
}
int code = 0;
char *response = NULL;
if (api_key) {
const char *headers[4] = { "Content-Type: application/json", NULL, NULL, NULL };
char *auth_header = NULL;
char *version_header = NULL;
if (strcmp (provider, "anthropic") == 0) {
// Anthropic uses different header format
auth_header = r_str_newf ("x-api-key: %s", api_key);
version_header = strdup ("anthropic-version: 2023-06-01");
headers[1] = auth_header;
headers[2] = version_header;
} else {
// Standard OpenAI-compatible format
auth_header = r_str_newf ("Authorization: Bearer %s", api_key);
headers[1] = auth_header;
}
// Make HTTP GET request
R_LOG_DEBUG ("GET %s Headers: %s", models_url, headers);
response = r2ai_http_get (models_url, headers, &code, NULL);
free (auth_header);
free (version_header);
free (api_key);
} else {
// We have no headers
R_LOG_DEBUG ("GET %s", models_url);
response = r2ai_http_get (models_url, NULL, &code, NULL);
}
free (models_url);
if (!response || code != 200) {
R_LOG_DEBUG ("Failed to fetch models from %s (code: %d)", provider, code);
free (response);
return NULL;
}
// Parse JSON response
RList *models = r_list_newf (free);
if (!models) {
free (response);
return NULL;
}
RJson *json = r_json_parse (response);
if (json) {
const RJson *data;
if (strcmp (provider, "ollama") != 0) {
data = r_json_get (json, "data");
} else {
data = r_json_get (json, "models");
}
if (data && data->type == R_JSON_ARRAY) {
const RJson *model_item = data->children.first;
while (model_item) {
const RJson *id;
if (strcmp (provider, "ollama") != 0) {
id = r_json_get (model_item, "id");
} else {
id = r_json_get (model_item, "model");
}
if (id && id->type == R_JSON_STRING && R_STR_ISNOTEMPTY (id->str_value)) {
R_LOG_DEBUG ("Model: %s", id->str_value);
r_list_append (models, strdup (id->str_value));
}
model_item = model_item->next;
}
}
r_json_free (json);
}
free (response);
return models;
}
static bool cb_r2ai_api(void *user, void *data) {
RCore *core = (RCore *)user;
RConfigNode *node = (RConfigNode *)data;
if (*node->value == '?') {
r_cons_println ("ollama");
r_cons_println ("openai");
r_cons_println ("openapi");
r_cons_println ("anthropic");
r_cons_println ("gemini");
r_cons_println ("openrouter");
r_cons_println ("mistral");
r_cons_println ("groq");
r_cons_println ("xai");
const char apis[] = "ollama\nopenai\nopenapi\nanthropic\ngemini\nopenrouter\nmistral\ngroq\nxai";
R2_PRINTLN (apis);
return false;
}
return true;
@@ -788,40 +853,39 @@ static bool cb_r2ai_model(void *user, void *data) {
RCore *core = (RCore *)user;
const char *api = r_config_get (core->config, "r2ai.api");
if (*node->value == '?') {
if (!strcmp (api, "anthropic")) {
r_cons_println ("claude-3-7-sonnet-20250219");
r_cons_println ("claude-3-5-sonnet-20241022");
r_cons_println ("claude-3-haiku-20240307");
} else if (!strcmp (api, "gemini")) {
r_cons_println ("gemini-1.5-flash");
r_cons_println ("gemini-1.0-pro");
} else if (!strcmp (api, "openai")) {
r_cons_println ("gpt-4");
r_cons_println ("gpt-4o-mini");
r_cons_println ("gpt-3.5-turbo");
} else if (!strcmp (api, "mistral")) {
r_cons_println ("mistral-large-latest");
r_cons_println ("codestral-latest");
} else if (!strcmp (api, "groq")) {
r_cons_println ("qwen-2.5-coder-32b");
} else if (!strcmp (api, "ollama")) {
char *s = r_sys_cmd_str ("ollama ls", NULL, NULL);
if (s) {
RList *items = r_str_split_list (s, "\n", 0);
RListIter *iter;
char *item;
r_list_foreach (items, iter, item) {
if (R_STR_ISEMPTY (item) || r_str_startswith (item, "NAME")) {
continue;
// Try to fetch models dynamically first
RList *models = fetch_available_models (core, api);
if (models && !r_list_empty (models)) {
RListIter *iter;
char *model;
r_list_foreach (models, iter, model) {
R2_PRINTLN (model);
}
r_list_free (models);
} else {
// Fallback to static lists if dynamic fetching fails
if (!strcmp (api, "gemini")) {
R2_PRINTLN ("gemini-1.5-flash");
R2_PRINTLN ("gemini-1.0-pro");
} else if (!strcmp (api, "ollama")) {
char *s = r_sys_cmd_str ("ollama ls", NULL, NULL);
if (s) {
RList *items = r_str_split_list (s, "\n", 0);
RListIter *iter;
char *item;
r_list_foreach (items, iter, item) {
if (R_STR_ISEMPTY (item) || r_str_startswith (item, "NAME")) {
continue;
}
char *s = strchr (item, ' ');
if (s) {
*s = 0;
}
R2_PRINTLN (item);
}
char *s = strchr (item, ' ');
if (s) {
*s = 0;
}
r_cons_println (item);
r_list_free (items);
free (s);
}
r_list_free (items);
free (s);
}
}
return false;
@@ -829,27 +893,45 @@ static bool cb_r2ai_model(void *user, void *data) {
return true;
}
#if R2_VERSION_NUMBER >= 50909
static bool r2ai_init(RCorePluginSession *cps) {
RCore *core = cps->core;
#else
static int r2ai_init(void *user, const char *input) {
RCmd *cmd = (RCmd *)user;
RCore *core = cmd->data;
#endif
// Initialize conversation container
r2ai_conversation_init ();
r_config_lock (core->config, false);
r_config_set_cb (core->config, "r2ai.api", "openai", &cb_r2ai_api);
r_config_set_cb (core->config, "r2ai.model", "gpt-4o-mini", &cb_r2ai_model);
r_config_set (core->config, "r2ai.base_url", "");
r_config_set_i (core->config, "r2ai.max_tokens", 5128);
r_config_desc (core->config, "r2ai.api", "LLM provider to use (openai, gemini, anthropic, ollama, ...)");
r_config_set_cb (core->config, "r2ai.model", "gpt-5-mini", &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", "");
r_config_desc (core->config, "r2ai.baseurl", "Base URL for provider API (overrides default endpoints)");
r_config_set_i (core->config, "r2ai.max_tokens", 4096); // max output tokens, or max total tokens
r_config_desc (core->config, "r2ai.max_tokens", "Maximum tokens for LLM responses (output/total depending on provider)");
r_config_set_i (core->config, "r2ai.thinking_tokens", 0);
r_config_desc (core->config, "r2ai.thinking_tokens", "Number of tokens reserved for internal thinking/context messages");
r_config_set (core->config, "r2ai.temperature", "0.01");
r_config_desc (core->config, "r2ai.temperature", "Sampling temperature for LLM output (0 = deterministic)");
r_config_set (core->config, "r2ai.cmds", "pdc");
r_config_desc (core->config, "r2ai.cmds", "Default command sequence used by automation (e.g. 'pdc')");
r_config_set (core->config, "r2ai.lang", "C");
r_config_desc (core->config, "r2ai.lang", "Programming language hint used in prompts (e.g. C, C++, Rust)");
r_config_set_b (core->config, "r2ai.data", false);
r_config_desc (core->config, "r2ai.data", "Enable local data/embeddings for query context retrieval");
r_config_set_b (core->config, "r2ai.data.reason", false);
r_config_desc (core->config, "r2ai.data.reason", "Include reasoning/explanations from local data when building context");
r_config_set (core->config, "r2ai.data.path", "/tmp/embeds");
r_config_desc (core->config, "r2ai.data.path", "Path to local embeddings/text files used for context (one .txt per document)");
r_config_set_i (core->config, "r2ai.data.nth", 10);
r_config_desc (core->config, "r2ai.data.nth", "Number of top-matching documents to include from local data for context");
r_config_set (core->config, "r2ai.hlang", "english");
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 "
@@ -862,24 +944,42 @@ static int r2ai_init(void *user, const char *input) {
"possible, use better variable names, take function arguments and "
"strings from comments like 'string:'");
r_config_set_b (core->config, "r2ai.stream", false);
r_config_desc (core->config, "r2ai.stream", "Enable streaming responses from the LLM (true/false)");
r_config_set_i (core->config, "r2ai.auto.max_runs", 50);
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_b (core->config, "r2ai.auto.ask_to_execute", true);
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");
r_config_set_b (core->config, "r2ai.auto.reset_on_query", false);
r_config_desc (core->config, "r2ai.auto.reset_on_query", "Reset auto-mode conversation state on new user queries");
r_config_set_b (core->config, "r2ai.chat.show_cost", true);
// Configure HTTP timeout in seconds
r_config_desc (core->config, "r2ai.chat.show_cost", "Display estimated API cost for chat interactions");
r_config_set_i (core->config, "r2ai.http.timeout", 120);
// Configure HTTP rate limiting and retry parameters
r_config_desc (core->config, "r2ai.http.timeout", "HTTP client timeout (seconds) for provider API calls");
r_config_set_i (core->config, "r2ai.http.max_retries", 10);
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_lock (core->config, true);
return true;
}
#if R2_VERSION_NUMBER >= 50909
static bool r2ai_fini(RCorePluginSession *cps) {
RCore *core = cps->core;
#else
static int r2ai_fini(void *user, const char *input) {
RCmd *cmd = (RCmd *)user;
RCore *core = cmd->data;
#endif
r_config_lock (core->config, false);
r_config_rm (core->config, "r2ai.api");
r_config_rm (core->config, "r2ai.cmds");
@@ -894,6 +994,8 @@ static int r2ai_fini(void *user, const char *input) {
r_config_rm (core->config, "r2ai.http.timeout");
r_config_rm (core->config, "r2ai.http.max_retries");
r_config_rm (core->config, "r2ai.http.max_backoff");
r_config_rm (core->config, "r2ai.http.backend");
r_config_rm (core->config, "r2ai.http.use_files");
r_config_lock (core->config, true);
// Free the conversation
@@ -910,8 +1012,13 @@ static int r2ai_fini(void *user, const char *input) {
return true;
}
#if R2_VERSION_NUMBER >= 50909
static bool r_cmd_r2ai_client(RCorePluginSession *cps, const char *input) {
RCore *core = cps->core;
#else
static int r_cmd_r2ai_client(void *user, const char *input) {
RCore *core = (RCore *)user;
#endif
if (r_str_startswith (input, "r2ai")) {
cmd_r2ai (core, r_str_trim_head_ro (input + 4));
return true;
@@ -925,7 +1032,7 @@ RCorePlugin r_core_plugin_r2ai_client = {
.name = "r2ai",
.desc = "r2ai plugin in plain C",
.author = "pancake",
.version = "0.9.6",
.version = "1.1.1",
.license = "MIT",
},
.init = r2ai_init,
@@ -934,7 +1041,12 @@ RCorePlugin r_core_plugin_r2ai_client = {
};
#ifndef R2_PLUGIN_INCORE
R_API RLibStruct radare_plugin = { .type = R_LIB_TYPE_CORE,
R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_r2ai_client,
.version = R2_VERSION };
#if R2_VERSION_NUMBER >= 50909
.abiversion = R2_ABIVERSION,
#endif
.version = R2_VERSION
};
#endif
+73 -33
View File
@@ -6,6 +6,20 @@
#include "r_vdb.h"
#include "markdown.h"
#if R2_VERSION_NUMBER >= 50909
#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)
#else
#define R2_PRINTF(...) r_cons_printf (__VA_ARGS__)
#define R2_FLUSH() r_cons_flush ()
#define R2_NEWLINE() r_cons_newline()
#define R2_PRINTLN(x) r_cons_println(x)
#define R2_INTERRUPTED() r_cons_is_breaked ()
#endif
// Tool definition structure
typedef struct {
char *name;
@@ -52,9 +66,9 @@ typedef struct {
} R2AI_Message;
typedef struct {
u_int64_t prompt_tokens;
u_int64_t completion_tokens;
u_int64_t total_tokens;
uint64_t prompt_tokens;
uint64_t completion_tokens;
uint64_t total_tokens;
} R2AI_Usage;
typedef struct {
@@ -87,150 +101,176 @@ typedef struct {
/**
* Initialize a new empty messages array
*/
R_API R2AI_Messages *r2ai_msgs_new (void);
R_API R2AI_Messages *r2ai_msgs_new(void);
/**
* Initialize the conversation container (call during plugin init)
*/
R_API void r2ai_conversation_init (void);
R_API void r2ai_conversation_init(void);
/**
* Get the conversation instance (returns NULL if not initialized)
*/
R_API R2AI_Messages *r2ai_conversation_get (void);
R_API R2AI_Messages *r2ai_conversation_get(void);
/**
* Clear all messages in a container without freeing the container
*/
R_API void r2ai_msgs_clear (R2AI_Messages *msgs);
R_API void r2ai_msgs_clear(R2AI_Messages *msgs);
/**
* Add a message to the array
* All strings are duplicated, so caller can free their copies
*/
R_API bool r2ai_msgs_add (R2AI_Messages *msgs, const R2AI_Message *msg);
R_API bool r2ai_msgs_add(R2AI_Messages *msgs, const R2AI_Message *msg);
/**
* Add a tool call to the last message in the array
* All strings are duplicated, so caller can free their copies
*/
R_API bool r2ai_msgs_add_tool_call (R2AI_Messages *msgs, const R2AI_ToolCall *tc);
R_API bool r2ai_msgs_add_tool_call(R2AI_Messages *msgs, const R2AI_ToolCall *tc);
/**
* Parse a JSON response string and add the messages to the array
* Returns true on success, false on failure
*/
R_API bool r2ai_msgs_from_response (R2AI_Messages *msgs, const char *json_str);
R_API bool r2ai_msgs_from_response(R2AI_Messages *msgs, const char *json_str);
/**
* Parse a RJson object directly and add the messages to the array
* Returns true on success, false on failure
*/
R_API bool r2ai_msgs_from_json (R2AI_Messages *msgs, const RJson *json);
R_API bool r2ai_msgs_from_json(R2AI_Messages *msgs, const RJson *json);
/**
* Convert messages array to JSON string
* Caller must free the returned string
*/
R_API char *r2ai_msgs_to_json (const R2AI_Messages *msgs);
R_API char *r2ai_msgs_to_json(const R2AI_Messages *msgs);
/**
* Convert messages array to Anthropic format JSON string
* Caller must free the returned string
*/
R_API char *r2ai_msgs_to_anthropic_json (const R2AI_Messages *msgs);
R_API char *r2ai_msgs_to_anthropic_json(const R2AI_Messages *msgs);
/**
* Free a messages array and all associated data
*/
R_API void r2ai_msgs_free (R2AI_Messages *msgs);
R_API void r2ai_msgs_free(R2AI_Messages *msgs);
/**
* Free the conversation (call during plugin unload)
*/
R_API void r2ai_conversation_free (void);
R_API void r2ai_conversation_free(void);
/**
* Free a R2AI_Message structure
*/
R_API void r2ai_message_free (R2AI_Message *msg);
R_API void r2ai_message_free(R2AI_Message *msg);
/**
* Delete the last N messages from the message array
* If n <= 0, defaults to deleting just the last message
*/
R_API void r2ai_delete_last_messages (R2AI_Messages *messages, int n);
R_API void r2ai_delete_last_messages(R2AI_Messages *messages, int n);
/**
* Get the global tools instance
* Returns a pointer to the global tools structure
*/
R_API const R2AI_Tools *r2ai_get_tools (void);
R_API const R2AI_Tools *r2ai_get_tools(void);
/**
* Parse OpenAI format tools JSON into internal tools structure
* Caller must free the result with r2ai_tools_free
*/
R_API R2AI_Tools *r2ai_tools_parse (const char *tools_json);
R_API R2AI_Tools *r2ai_tools_parse(const char *tools_json);
/**
* Convert tools structure to OpenAI format JSON
* Caller must free the returned string
*/
R_API char *r2ai_tools_to_openai_json (const R2AI_Tools *tools);
R_API char *r2ai_tools_to_openai_json(const R2AI_Tools *tools);
/**
* Convert tools structure to Anthropic format JSON
* Caller must free the returned string
*/
R_API char *r2ai_tools_to_anthropic_json (const R2AI_Tools *tools);
R_API char *r2ai_tools_to_anthropic_json(const R2AI_Tools *tools);
/**
* Free a tools structure and all associated data
*/
R_API void r2ai_tools_free (R2AI_Tools *tools);
R_API void r2ai_tools_free(R2AI_Tools *tools);
/**
* Execute a tool and return the output
* Execute a tool and return the output
* The command to execute is within args. The user may interactively
* modify this command, hence edited_command contains the real
* command which was run
* Caller must free edited_command
*/
R_API char *execute_tool (RCore *core, const char *tool_name, const char *args);
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, char **edited_command);
/**
* Send an HTTP POST request
*
* @param core The RCore instance
* @param url The URL to send the request to
* @param headers Array of headers, NULL terminated
* @param data The data to send in the request
* @param code Pointer to store the response code
* @param rlen Pointer to store the response length
* @param use_files Use files instead of arguments
* @return Response body as string (must be freed by caller) or NULL on error
*/
R_API char *r2ai_http_post (const char *url, const char *headers[], const char *data, int *code, int *rlen);
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen);
/**
* Send an HTTP GET request
*
* @param url The URL to send the request to
* @param headers Array of headers, NULL terminated
* @param code Pointer to store the response code
* @param rlen Pointer to store the response length
* @return Response body as string (must be freed by caller) or NULL on error
*/
R_API char *r2ai_http_get(const char *url, const char *headers[], int *code, int *rlen);
/**
* Get the base URL for a given provider
*
* @param core RCore instance for configuration
* @param provider The provider name (e.g., "openai", "anthropic", etc.)
* @return Base URL for the provider, or NULL if unknown
*/
R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider);
// anthropic
R_IPI R2AI_ChatResponse *r2ai_anthropic (RCore *core, R2AIArgs args);
R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args);
// openai
R_IPI R2AI_ChatResponse *r2ai_openai (RCore *core, R2AIArgs args);
R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args);
R_IPI void r2ai_openai_fini(void);
// auto mode
R_IPI void cmd_r2ai_a (RCore *core, const char *user_query);
R_IPI char *r2ai (RCore *core, R2AIArgs args);
R_IPI void cmd_r2ai_a(RCore *core, const char *user_query);
R_API char *r2ai(RCore *core, R2AIArgs args);
R_IPI R2AI_ChatResponse *r2ai_llmcall (RCore *core, R2AIArgs args);
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args);
R_IPI void cmd_r2ai_logs (RCore *core);
R_IPI void cmd_r2ai_logs(RCore *core);
/**
* Create a conversation with system prompt and optional user message
*/
R_API R2AI_Messages *create_conversation (const char *user_message);
R_API R2AI_Messages *create_conversation(const char *user_message);
/**
* Process messages through LLM and handle tool calls recursively
*/
R_API void process_messages (RCore *core, R2AI_Messages *messages, const char *system_prompt, int n_run);
R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *system_prompt, int n_run);
/**
* Helper function to convert RJson to string
+656 -21
View File
@@ -20,7 +20,25 @@ static volatile sig_atomic_t r2ai_http_interrupted = 0;
// Signal handler for SIGINT
static void r2ai_http_sigint_handler(int sig) {
r2ai_http_interrupted = 1;
(void)sig;
r2ai_http_interrupted = 1;
}
/*
* Portable install/restore helpers for SIGINT handler.
* Some platforms may not have struct sigaction available; use signal()
* as a fallback. We allocate storage for the old handler when
* sigaction is available so the restore function can restore it later.
*/
static void install_sigint_handler_local(void **out_old, int *out_old_is_sigaction) {
void (*old)(int) = signal (SIGINT, r2ai_http_sigint_handler);
*out_old = (void *)old;
*out_old_is_sigaction = 0;
}
static void restore_sigint_handler_local(void *old, int old_is_sigaction) {
(void)old_is_sigaction;
signal (SIGINT, (void (*)(int))old);
}
// Helper function to implement exponential backoff sleep
@@ -126,12 +144,10 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
}
}
// Install signal handler for interruption
struct sigaction new_action, old_action;
new_action.sa_handler = r2ai_http_sigint_handler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction (SIGINT, &new_action, &old_action);
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
@@ -155,7 +171,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
r2ai_sleep_with_backoff (retry_count, max_backoff);
continue;
}
sigaction (SIGINT, &old_action, NULL); // Restore signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
return NULL;
}
response.data[0] = '\0';
@@ -169,7 +185,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
r2ai_sleep_with_backoff (retry_count, max_backoff);
continue;
}
sigaction (SIGINT, &old_action, NULL); // Restore signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
return NULL;
}
@@ -264,12 +280,285 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
}
// Restore the original signal handler
sigaction (SIGINT, &old_action, NULL);
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
#endif // HAVE_LIBCURL
/**
* Execute curl as a system command, sending data via temporary file
*
* @param url The URL to send the request to
* @param headers Array of headers, NULL terminated
* @param data The data to send in the request
* @param code Pointer to store the response code
* @param rlen Pointer to store the response length
* @return Response body as string (must be freed by caller) or NULL on error
*/
static char *system_curl_post_file(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen, bool use_files) {
if (!url || !headers || !data || !code) {
return NULL;
}
int timeout = r_config_get_i (core->config, "r2ai.http.timeout");
if (timeout <= 0) {
timeout = 120; // Use default if invalid
}
int max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
if (max_retries < 0) {
max_retries = 10; // Use default if invalid
}
int max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
if (max_backoff <= 0) {
max_backoff = 30; // Use default if invalid
}
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
// Retry loop
char *result = NULL;
int retry_count = 0;
bool success = false;
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
// Create a temporary file for the data
char *temp_file = r_file_temp ("r2ai_data");
if (!temp_file) {
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);
continue;
}
break;
}
if (use_files) {
// Write data to the temporary file
if (!r_file_dump (temp_file, (const ut8 *)data, strlen (data), 0)) {
R_LOG_ERROR ("Failed to write data to temporary file");
free (temp_file);
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
continue;
}
break;
}
}
// Compose curl command
RStrBuf *cmd = r_strbuf_new ("curl -s");
// Add timeout
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
// Add headers
for (int i = 0; headers[i] != NULL; i++) {
r_strbuf_appendf (cmd, " -H \"%s\"", headers[i]);
}
if (use_files) {
// Add data file with @ prefix for curl
r_strbuf_appendf (cmd, " -X POST -d @%s \"%s\"", temp_file, url);
} else {
char *s = strdup (data);
s = r_str_replace_all (s, "\\", "\\\\");
// s = r_str_replace_all (s, "'", "\\'");
s = r_str_replace_all (s, "'", "'\\''");
r_strbuf_appendf (cmd, " -X POST -d '%s' \"%s\"", s, url);
free (s);
}
// Execute the curl command
char *cmd_str = r_strbuf_drain (cmd);
r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (timeout + 10); // Add a little extra for process startup
#endif
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
char *response = r_sys_cmd_str (cmd_str, NULL, NULL);
// Clear the alarm
#if R2__UNIX__
alarm (0);
#endif
free (cmd_str);
r_file_rm (temp_file);
free (temp_file);
// Check if we were interrupted
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
free (response);
response = NULL;
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return NULL;
}
// We can't easily get the HTTP status code using this method
// Let's assume 200 if we got a response, and 0 otherwise
if (response) {
*code = 200;
if (rlen) {
*rlen = strlen (response);
}
result = response;
success = true;
} 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);
continue;
}
*code = 0;
break;
}
}
// Restore the original signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
/**
* Execute curl as a system command for GET requests
*
* @param url The URL to send the request to
* @param headers Array of headers, NULL terminated
* @param code Pointer to store the response code
* @param rlen Pointer to store the response length
* @return Response body as string (must be freed by caller) or NULL on error
*/
static char *system_curl_get(const char *url, const char *headers[], int *code, int *rlen) {
if (!url || !code) {
return NULL;
}
// Get timeout and retry configuration from config if available
int timeout = 120; // Default timeout in seconds
int max_retries = 10; // Default max retries
int max_backoff = 30; // Default max backoff in seconds
RCore *core = r_cons_singleton ()->user;
if (core) {
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
if (timeout <= 0) {
timeout = 120; // Use default if invalid
}
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
if (max_retries < 0) {
max_retries = 10; // Use default if invalid
}
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
if (max_backoff <= 0) {
max_backoff = 30; // Use default if invalid
}
}
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
// Retry loop
char *result = NULL;
int retry_count = 0;
bool success = false;
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
// Compose curl command
RStrBuf *cmd = r_strbuf_new ("curl -s");
// Add timeout
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
// Add headers
if (headers) {
for (int i = 0; headers[i] != NULL; i++) {
r_strbuf_appendf (cmd, " -H \"%s\"", headers[i]);
}
}
// Add URL
r_strbuf_appendf (cmd, " \"%s\"", url);
// Execute the curl command
char *cmd_str = r_strbuf_drain (cmd);
r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (timeout + 10); // Add a little extra for process startup
#endif
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
char *response = r_sys_cmd_str (cmd_str, NULL, NULL);
// Clear the alarm
#if R2__UNIX__
alarm (0);
#endif
free (cmd_str);
// Check if we were interrupted
if (r2ai_http_interrupted) {
R_LOG_DEBUG ("HTTP request was interrupted by user");
free (response);
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return NULL;
}
// We can't easily get the HTTP status code using this method
// Let's assume 200 if we got a response, and 0 otherwise
if (response) {
*code = 200;
if (rlen) {
*rlen = strlen (response);
}
result = response;
success = true;
} 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);
continue;
}
*code = 0;
break;
}
}
// Restore the original signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
// Socket implementation with interrupt handling and retry logic
static char *socket_http_post_with_interrupt(const char *url, const char *headers[], const char *data, int *code, int *rlen) {
// Get timeout and retry configuration from config if available
@@ -295,12 +584,10 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
}
}
// Install signal handler for interruption
struct sigaction new_action, old_action;
new_action.sa_handler = r2ai_http_sigint_handler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction (SIGINT, &new_action, &old_action);
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
@@ -312,8 +599,10 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (timeout); // Use configured timeout
#endif
// Make the request
#if R2_VERSION_NUMBER >= 50909
@@ -323,7 +612,9 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
#endif
// Clear the alarm
#if R2__UNIX__
alarm (0);
#endif
// Check if we were interrupted
if (r2ai_http_interrupted) {
@@ -366,18 +657,362 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
}
// Restore the original signal handler
sigaction (SIGINT, &old_action, NULL);
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
R_API char *r2ai_http_post(const char *url, const char *headers[], const char *data, int *code, int *rlen) {
#if HAVE_LIBCURL
static char *curl_http_get(const char *url, const char *headers[], int *code, int *rlen) {
if (!url || !code) {
return NULL;
}
// Get timeout and retry configuration from config if available
int timeout = 120; // Default timeout in seconds
int max_retries = 10; // Default max retries
int max_backoff = 30; // Default max backoff in seconds
RCore *core = r_cons_singleton ()->user;
if (core) {
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
if (timeout <= 0) {
timeout = 120; // Use default if invalid
}
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
if (max_retries < 0) {
max_retries = 10; // Use default if invalid
}
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
if (max_backoff <= 0) {
max_backoff = 30; // Use default if invalid
}
}
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
// Retry loop
char *result = NULL;
int retry_count = 0;
bool success = false;
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
CURL *curl;
CURLcode res;
struct curl_slist *curl_headers = NULL;
CurlResponse response = { 0 };
// Initialize response
response.data = malloc (1);
if (!response.data) {
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
return NULL;
}
response.data[0] = '\0';
response.size = 0;
curl = curl_easy_init ();
if (!curl) {
free (response.data);
if (retry_count < max_retries) {
retry_count++;
r2ai_sleep_with_backoff (retry_count, max_backoff);
continue;
}
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
return NULL;
}
// Set URL
curl_easy_setopt (curl, CURLOPT_URL, url);
// This is a GET request - no POST data
curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
// Set headers if provided
if (headers) {
for (int i = 0; headers[i] != NULL; i++) {
curl_headers = curl_slist_append (curl_headers, 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)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);
break; // Exit the retry loop
}
// Get response code
long http_code;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
*code = (int)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);
// Retry on network errors
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);
continue;
}
break; // Exit the retry loop after max retries
}
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
if (http_code == 429 || (http_code >= 500 && http_code < 600)) {
R_LOG_WARN ("Server returned %d response code", (int)http_code);
free (response.data);
curl_slist_free_all (curl_headers);
curl_easy_cleanup (curl);
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);
continue;
}
break; // Exit the retry loop after max retries
}
// If we get here, the request was successful
success = true;
result = response.data;
if (rlen) {
*rlen = response.size;
}
// Cleanup
curl_slist_free_all (curl_headers);
curl_easy_cleanup (curl);
}
// Restore the original signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
#endif // HAVE_LIBCURL
// Socket implementation for GET requests
static char *socket_http_get_with_interrupt(const char *url, const char *headers[], int *code, int *rlen) {
// Get timeout and retry configuration from config if available
int timeout = 120; // Default timeout in seconds
int max_retries = 10; // Default max retries
int max_backoff = 30; // Default max backoff in seconds
RCore *core = r_cons_singleton ()->user;
if (core) {
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
if (timeout <= 0) {
timeout = 120; // Use default if invalid
}
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
if (max_retries < 0) {
max_retries = 10; // Use default if invalid
}
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
if (max_backoff <= 0) {
max_backoff = 30; // Use default if invalid
}
}
// Install signal handler for interruption (portable)
void *r2ai_old_sig = NULL;
int r2ai_old_is_sigaction = 0;
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
// Reset interrupt flag
r2ai_http_interrupted = 0;
// Retry loop
char *result = NULL;
int retry_count = 0;
bool success = false;
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
// Set an alarm to limit the request time
#if R2__UNIX__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (timeout); // Use configured timeout
#endif
// Make the request - use r_socket_http_get if available
#if R2_VERSION_NUMBER >= 50909
result = r_socket_http_get (url, headers, code, rlen);
#else
// For older radare2 versions, we might need to use a different approach
// or create a custom implementation
result = NULL;
*code = 0;
#endif
// 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);
result = NULL;
break; // Exit the retry loop
}
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
if (result && *code) {
if (*code == 429 || (*code >= 500 && *code < 600)) {
R_LOG_WARN ("Server returned %d response code", *code);
free (result);
result = NULL;
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);
continue;
}
break; // Exit the retry loop after max retries
}
}
// Check for other failures
if (!result) {
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);
continue;
}
break; // Exit the retry loop after max retries
}
// If we get here, the request was successful
success = true;
}
// Restore the original signal handler
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
return result;
}
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen) {
const char *backend = "auto";
bool use_files = false;
if (core) {
const char *config_backend = r_config_get (core->config, "r2ai.http.backend");
if (config_backend) {
backend = config_backend;
}
use_files = r_config_get_b (core->config, "r2ai.http.use_files");
}
// Choose implementation based on backend config
if (!strcmp (backend, "system")) {
// Always use system curl with files for POST requests
return system_curl_post_file (core, url, headers, data, code, rlen, use_files);
} else if (!strcmp (backend, "libcurl")) {
#if USE_LIBCURL && HAVE_LIBCURL
return curl_http_post (url, headers, data, code, rlen);
return curl_http_post (url, headers, data, code, rlen);
#else
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
#endif
} else if (!strcmp (backend, "socket")) {
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
} else {
// Auto-select best available implementation
if (use_files) {
// If use_files is true, always use system curl
return system_curl_post_file (core, url, headers, data, code, rlen, use_files);
} else {
#if USE_LIBCURL && HAVE_LIBCURL
return curl_http_post (url, headers, data, code, rlen);
#else
#if USE_R2_CURL
r_sys_setenv ("R2_CURL", "1");
r_sys_setenv ("R2_CURL", "1");
#endif
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
#endif
}
}
}
R_API char *r2ai_http_get(const char *url, const char *headers[], int *code, int *rlen) {
RCore *core = r_cons_singleton ()->user;
const char *backend = "auto";
if (core) {
backend = r_config_get (core->config, "r2ai.http.backend");
}
// Choose implementation based on backend config
if (!strcmp (backend, "system")) {
// Use system curl for GET requests
return system_curl_get (url, headers, code, rlen);
} else if (!strcmp (backend, "libcurl")) {
#if USE_LIBCURL && HAVE_LIBCURL
return curl_http_get (url, headers, code, rlen);
#else
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
return socket_http_get_with_interrupt (url, headers, code, rlen);
#endif
} else if (!strcmp (backend, "socket")) {
return socket_http_get_with_interrupt (url, headers, code, rlen);
} else {
// Auto-select best available implementation
#if USE_LIBCURL && HAVE_LIBCURL
return curl_http_get (url, headers, code, rlen);
#else
#if USE_R2_CURL
r_sys_setenv ("R2_CURL", "1");
#endif
return socket_http_get_with_interrupt (url, headers, code, rlen);
#endif
}
}
-21
View File
@@ -1,21 +0,0 @@
}
args.provider = strdup(provider);
const char *api_key_env = r_str_newf("%s_API_KEY", provider);
r_str_case(api_key_env, true);
const char *api_key_config = r_str_newf("r2ai.%s.api_key", provider);
const char *api_key_filename = r_str_newf("~/.r2ai.%s-key", provider);
if (r_config_get(core->config, api_key_config)) {
args.api_key = strdup (r_config_get (core->config, api_key_config));
} else if (r_file_exists(api_key_filename)) {
char *apikey_file = r_file_new (api_key_filename, NULL);
args.api_key = r_file_slurp (apikey_file, NULL);
free (apikey_file);
} else if (getenv(api_key_env)) {
args.api_key = strdup (getenv (api_key_env));
}
r_str_trim(args.api_key);
R_LOG_INFO("Using provider: %s", provider);
if (strcmp(provider, "anthropic") == 0) {
res = r2ai_anthropic (core, args);
} else {
+53 -58
View File
@@ -1,3 +1,4 @@
#include <string.h>
#include "r2ai.h"
// Define the radare2 command tool
@@ -63,10 +64,6 @@ R_API R2AI_Tools *r2ai_tools_parse(const char *tools_json) {
}
R2AI_Tools *tools = R_NEW0 (R2AI_Tools);
if (!tools) {
r_json_free (json);
return NULL;
}
int n_tools = json->children.count;
tools->tools = R_NEWS0 (R2AI_Tool, n_tools);
@@ -261,7 +258,7 @@ static char *to_cmd(const char *command) {
return pj_drain (pj);
}
R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **edited_command) {
if (!args) {
return strdup ("{ \"res\":\"Command is NULL\" }");
}
@@ -277,8 +274,8 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
return strdup ("{ \"res\":\"You are already in r2!\" }");
}
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.ask_to_execute");
char *edited_command = NULL;
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.yolo") != true;
*edited_command = NULL; // keeps track of the command that was really executed in case user modifies it
if (ask_to_execute) {
// Check if command contains newlines to determine if it's multi-line
@@ -286,66 +283,67 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
if (is_multiline) {
// Use editor for multi-line commands
edited_command = strdup (command);
*edited_command = strdup (command);
#if R2_VERSION_NUMBER >= 50909
r_cons_editor (core->cons, NULL, edited_command);
r_cons_editor (core->cons, NULL, *edited_command);
#else
r_cons_editor (NULL, edited_command);
r_cons_editor (NULL, *edited_command);
#endif
command = edited_command;
command = *edited_command;
} else {
// For single-line commands, push the command to input buffer
r_cons_newline ();
r_cons_printf ("\x1b[31m[edit cmd]>\x1b[0m ");
r_cons_flush ();
R2_NEWLINE ();
R2_PRINTF ("\x1b[31m[edit cmd]>\x1b[0m ");
R2_FLUSH ();
// Push the command to the input buffer
r_cons_readpush (command, strlen (command));
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
// 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 (r_cons_is_breaked () || !readline_result || !*readline_result) {
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
R_LOG_INFO ("Command execution cancelled %s", readline_result);
return strdup ("R2AI_SIGINT");
}
// Process the result
if (readline_result && *readline_result) {
edited_command = strdup (readline_result);
command = edited_command;
if (R_STR_ISNOTEMPTY (readline_result)) {
*edited_command = strdup (readline_result);
command = *edited_command;
} else {
// If user just pressed enter, keep the original command
edited_command = strdup (command);
command = edited_command;
*edited_command = strdup (command);
command = *edited_command;
}
}
R_LOG_DEBUG ("Edited command: %s", *edited_command);
} else {
*edited_command = strdup (command);
}
if (!hide_tool_output) {
char *red_command = r_str_newf ("\x1b[31m%s\x1b[0m\n", command);
r_cons_write (red_command, strlen (red_command));
r_cons_flush ();
char *red_command = r_str_newf ("\x1b[31m%s\x1b[0m\n", *edited_command);
R2_PRINTF ("%s", red_command);
R2_FLUSH ();
free (red_command);
}
char *json_cmd = to_cmd (command);
char *json_cmd = to_cmd (*edited_command);
if (!json_cmd) {
free (edited_command); // Free edited_command if allocated
// caller should free edited_command
return strdup ("{ \"res\":\"Failed to create JSON command\" }");
}
char *cmd_output = r_core_cmd_str (core, json_cmd);
free (json_cmd);
// Free edited_command if we allocated it
free (edited_command);
if (!cmd_output) {
return strdup ("{ \"res\":\"Command returned no output or failed\" }");
}
@@ -353,11 +351,6 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
return cmd_output;
}
// R_API int r_base64_encode(char *bout, const ut8 *bin, int len);
// R_API int r_base64_decode(ut8 *bout, const char *bin, int len);
// R_API ut8 *r_base64_decode_dyn(const char *in, int len);
// R_API char *r_base64_encode_dyn(const char *str, int len);
// qjs function implementation
R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
if (!args) {
@@ -373,7 +366,7 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
return strdup ("{ \"res\":\"Script value is NULL or empty\" }");
}
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.ask_to_execute");
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.yolo") != true;
const char *script = script_json->str_value;
char *edited_script = NULL;
@@ -392,22 +385,21 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
script = edited_script;
} else {
// For single-line scripts, push the script to input buffer
r_cons_printf ("\x1b[31m[edit js]>\x1b[0m ");
r_cons_flush ();
R2_PRINTF ("\x1b[31m[edit js]>\x1b[0m ");
R2_FLUSH ();
// Push the script to the input buffer
r_cons_readpush (script, strlen (script));
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
// Get user input with script pre-filled
#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 (r_cons_is_breaked () || !readline_result || !*readline_result) {
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
free (edited_script); // Free if already allocated
return strdup ("R2AI_SIGINT");
}
@@ -427,8 +419,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);
r_cons_write (print_script_rendered, strlen (print_script_rendered));
r_cons_flush ();
R2_PRINTF ("%s\n", print_script_rendered);
R2_FLUSH ();
free (print_script);
free (print_script_rendered);
}
@@ -443,7 +435,11 @@ 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\" }");
@@ -467,7 +463,7 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
return cmd_output;
}
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, char **edited_command) {
if (!tool_name || !args) {
return strdup ("{ \"res\":\"Tool name or arguments are NULL\" }");
}
@@ -487,14 +483,14 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
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);
r_cons_write (print_name, strlen (print_name));
r_cons_flush ();
R2_PRINTF ("%s\n", print_name);
R2_FLUSH ();
free (print_name);
char *tool_result = NULL;
if (strcmp (tool_name, "r2cmd") == 0) {
tool_result = r2ai_r2cmd (core, args_json, hide_tool_output);
tool_result = r2ai_r2cmd (core, args_json, hide_tool_output, edited_command);
} else if (strcmp (tool_name, "execute_js") == 0) {
tool_result = r2ai_qjs (core, args_json, hide_tool_output);
} else {
@@ -511,7 +507,7 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
char *result = NULL;
// Check for empty or invalid response that could cause a crash
if (!tool_result || !*tool_result) {
if (R_STR_ISEMPTY (tool_result)) {
free (tool_result);
return strdup ("{ \"res\":\"Error: Empty or invalid response from QJS execution\" }");
}
@@ -524,9 +520,8 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
return result;
}
RJson *json = NULL;
// Try to parse the JSON safely
json = r_json_parse (tool_result);
RJson *json = r_json_parse (tool_result);
if (!json) {
// JSON parsing failed, return original content
@@ -632,10 +627,10 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
return r_str_newf ("Error running tool: Unknown error\nCommand: %s", args);
}
if (!hide_tool_output) {
r_cons_newline ();
r_cons_write (result, strlen (result));
r_cons_newline ();
r_cons_flush ();
R2_NEWLINE ();
R2_PRINTF ("%s\n", result);
R2_NEWLINE ();
R2_FLUSH ();
}
r_str_ansi_strip (result);