mirror of
https://github.com/radareorg/r2ai.git
synced 2026-11-09 17:46:36 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b96f7a3e | ||
|
|
e2ee8cdbb6 | ||
|
|
8630e95d83 | ||
|
|
e07293e40f | ||
|
|
415c546f92 | ||
|
|
8446af7385 | ||
|
|
5a156a0126 | ||
|
|
a28efa7a33 | ||
|
|
bc5adb3fcb | ||
|
|
4bd1d911e0 | ||
|
|
41c9734c69 | ||
|
|
1a41298dd6 | ||
|
|
c618f8ed09 | ||
|
|
48932c5c64 | ||
|
|
4da20a5c33 | ||
|
|
b7f94ad419 | ||
|
|
2e45fb9e9c | ||
|
|
9775e94455 | ||
|
|
67416766e2 | ||
|
|
5c1be40048 | ||
|
|
49e87c7b7a | ||
|
|
11e7c288e0 | ||
|
|
909f4738f8 | ||
|
|
0148a64aa7 | ||
|
|
d8df27a4c7 | ||
|
|
6608dbcf3c | ||
|
|
ef83ddefc9 | ||
|
|
c19fd205e3 | ||
|
|
eec673f8db | ||
|
|
5e20b37e08 | ||
|
|
547418adf0 | ||
|
|
e161577bcb | ||
|
|
ba186d7c57 | ||
|
|
24c1e3fcb4 | ||
|
|
1f001080ff | ||
|
|
a189a682b6 | ||
|
|
a0299f791d | ||
|
|
79214fb22c | ||
|
|
775d3243e6 | ||
|
|
cd514ac099 | ||
|
|
6f352e1966 | ||
|
|
32458a653e | ||
|
|
b32fd6097f | ||
|
|
df2fc67e2c | ||
|
|
b318c30865 |
No files matched your search
+142
-78
@@ -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
|
||||
@@ -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
@@ -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
|
||||
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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)
|
||||
@@ -1,124 +1,80 @@
|
||||
```
|
||||
,______ .______ .______ ,___
|
||||
: __ \ \____ |: \ : __|
|
||||
| \____|/ ____|| _,_ || : |
|
||||
| : \ \ . || : || |
|
||||
| |___\ \__:__||___| || |
|
||||
|___| : |___||___|
|
||||
*
|
||||
# R2AI - Augmented reversing with LLM for radare2
|
||||
|
||||
```console
|
||||
╭─────────────────────────────────╮
|
||||
│ ,______ .______ .______ ,___ │
|
||||
╭──╮ │ : __ \ \____ |: \ : __| │
|
||||
│ _│_ │ | \____|/ ____|| _,_ || : | │
|
||||
│ O O < | : \ \ . || : || | │
|
||||
│ │╷ │ | |___\ \__:__||___| || | │
|
||||
│ ││ │ |___| : |___||___| │
|
||||
│ ─╯│ ╰─────────────────────────────────╯
|
||||
╰───╯
|
||||
```
|
||||
|
||||
[](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/images/r2clippy.jpg">
|
||||
</p>
|
||||
[](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`
|
||||
|
||||
For example:
|
||||
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
|
||||
$ cd src
|
||||
$ make
|
||||
$ 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:
|
||||
|
||||
```
|
||||
$ cat ~/.r2ai.anthropic-key
|
||||
sk-ant-api03-CENSORED
|
||||
```
|
||||
|
||||
### Saving configuration settings
|
||||
## 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.
|
||||
@@ -129,6 +85,11 @@ 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
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
vim src/r2ai.c
|
||||
vim VERSION
|
||||
+1176
-1212
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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
|
||||
Executable
+59
@@ -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
|
||||
@@ -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}
|
||||
```
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Executable
+231
@@ -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/&/\&/g; s/</\</g; s/>/\>/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
|
||||
@@ -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
|
||||
+12
-8
@@ -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
|
||||
@@ -34,11 +39,9 @@ OBJS+=anthropic.o
|
||||
OBJS+=r2ai_http.o
|
||||
OBJS+=openai.o
|
||||
OBJS+=markdown.o
|
||||
# DOTEXE=.exe
|
||||
DOTEXE=
|
||||
|
||||
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
|
||||
@@ -56,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 --
|
||||
|
||||
@@ -55,6 +55,28 @@ 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
|
||||
|
||||
- Get the [substitute example binary](https://github.com/radareorg/r2ai/tree/master/examples)
|
||||
@@ -234,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
|
||||
@@ -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;
|
||||
}
|
||||
+8
-6
@@ -29,9 +29,9 @@ static char *format_time_duration(time_t seconds) {
|
||||
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));
|
||||
(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,7 +66,7 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
if (r_config_get_b (core->config, "r2ai.chat.show_cost") == true) {
|
||||
// TODO: calculate cost
|
||||
stats.run_cost = 0.0 * run_time;
|
||||
@@ -188,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 call(s)", message->n_tool_calls);
|
||||
R_LOG_DEBUG ("Found %d tool call (s)", message->n_tool_calls);
|
||||
|
||||
// Process each tool call
|
||||
for (int i = 0; i < message->n_tool_calls; i++) {
|
||||
@@ -286,7 +288,7 @@ static void print_content_with_length(RCore *core, const char *content, const ch
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
+14
-6
@@ -1,23 +1,31 @@
|
||||
/* Copyright r2ai - 2023-2025 - pancake */
|
||||
|
||||
#include <r_core.h>
|
||||
#include "r2ai.h"
|
||||
|
||||
// TODO: not yet usable
|
||||
|
||||
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 = argv[1],
|
||||
.input = prompt,
|
||||
.error = &err,
|
||||
.dorag = false
|
||||
.dorag = false,
|
||||
};
|
||||
|
||||
char *res = r2ai (core, args);
|
||||
R_LOG_INFO (res);
|
||||
free (res);
|
||||
if (res) {
|
||||
R_LOG_INFO (res);
|
||||
free (res);
|
||||
}
|
||||
free (err);
|
||||
r_core_free (core);
|
||||
return 0;
|
||||
}
|
||||
+216
@@ -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]
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
option('r2_prefix',
|
||||
type: 'string',
|
||||
value: '',
|
||||
description: 'Path prefix where radare2 is installed (include/ and lib/).'
|
||||
)
|
||||
|
||||
+17
-32
@@ -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
|
||||
|
||||
+31
-8
@@ -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);
|
||||
@@ -147,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;
|
||||
@@ -185,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);
|
||||
@@ -260,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")) {
|
||||
@@ -349,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;
|
||||
@@ -402,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;
|
||||
}
|
||||
+142
-183
@@ -49,113 +49,56 @@ static void refresh_embeddings(RCore *core) {
|
||||
|
||||
static RCoreHelpMessage help_msg_r2ai = {
|
||||
"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",
|
||||
" -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",
|
||||
"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;
|
||||
@@ -164,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);
|
||||
|
||||
@@ -172,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;
|
||||
}
|
||||
}
|
||||
@@ -273,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]");
|
||||
@@ -326,7 +257,7 @@ static void cmd_r2ai_d(RCore *core, const char *input, const bool recursive) {
|
||||
const char *lang = r_config_get (core->config, "r2ai.lang");
|
||||
char *full_prompt;
|
||||
if (!R_STR_ISEMPTY (input)) {
|
||||
R_LOG_DEBUG("User question: %s", input);
|
||||
R_LOG_DEBUG ("User question: %s", input);
|
||||
full_prompt = strdup (input);
|
||||
} else {
|
||||
if (!R_STR_ISEMPTY (lang)) {
|
||||
@@ -767,24 +698,26 @@ static void cmd_r2ai(RCore *core, const char *input) {
|
||||
}
|
||||
|
||||
R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider) {
|
||||
const char *host = r_config_get (core->config, "r2ai.baseurl");
|
||||
|
||||
if (R_STR_ISNOTEMPTY (host)) {
|
||||
if (r_str_startswith (host, "http")) {
|
||||
return strdup (host);
|
||||
}
|
||||
if (strstr (host, "/v")) {
|
||||
return r_str_newf ("http://%s", host);
|
||||
}
|
||||
return r_str_newf ("http://%s/v1", host);
|
||||
}
|
||||
|
||||
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) {
|
||||
return "http://localhost:11434/v1";
|
||||
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) {
|
||||
@@ -809,42 +742,30 @@ static RList *fetch_available_models(RCore *core, const char *provider) {
|
||||
}
|
||||
|
||||
// Create models endpoint URL
|
||||
char *models_url = r_str_newf ("%s/models", purl);
|
||||
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") != 0 && strcmp (provider, "openapi") != 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);
|
||||
|
||||
char *s = r_sys_getenv (api_key_env_copy);
|
||||
if (R_STR_ISNOTEMPTY (s)) {
|
||||
api_key = s;
|
||||
} else {
|
||||
free (s);
|
||||
const 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 (absolute_apikey);
|
||||
}
|
||||
free (api_key_env_copy);
|
||||
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);
|
||||
}
|
||||
|
||||
// Setup headers based on provider
|
||||
const char *headers[4] = { "Content-Type: application/json", NULL, NULL, NULL };
|
||||
char *auth_header = NULL;
|
||||
char *version_header = NULL;
|
||||
|
||||
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);
|
||||
@@ -856,16 +777,20 @@ static RList *fetch_available_models(RCore *core, const char *provider) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Make HTTP GET request
|
||||
int code = 0;
|
||||
char *response = r2ai_http_get (models_url, headers, &code, NULL);
|
||||
|
||||
free (models_url);
|
||||
free (auth_header);
|
||||
free (version_header);
|
||||
free (api_key);
|
||||
|
||||
if (!response || code != 200) {
|
||||
R_LOG_DEBUG ("Failed to fetch models from %s (code: %d)", provider, code);
|
||||
@@ -882,12 +807,24 @@ static RList *fetch_available_models(RCore *core, const char *provider) {
|
||||
|
||||
RJson *json = r_json_parse (response);
|
||||
if (json) {
|
||||
const RJson *data = r_json_get (json, "data");
|
||||
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 = r_json_get (model_item, "id");
|
||||
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;
|
||||
@@ -970,18 +907,31 @@ static int r2ai_init(void *user, const char *input) {
|
||||
|
||||
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_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 "
|
||||
@@ -994,21 +944,30 @@ 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_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);
|
||||
r_config_desc (core->config, "r2ai.chat.show_cost", "Display estimated API cost for chat interactions");
|
||||
|
||||
// Configure HTTP timeout in seconds
|
||||
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);
|
||||
// Configure HTTP backend and options
|
||||
r_config_set (core->config, "r2ai.http.backend", "auto"); // Options: auto, libcurl, socket, system
|
||||
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;
|
||||
}
|
||||
@@ -1073,7 +1032,7 @@ RCorePlugin r_core_plugin_r2ai_client = {
|
||||
.name = "r2ai",
|
||||
.desc = "r2ai plugin in plain C",
|
||||
.author = "pancake",
|
||||
.version = "1.0.0",
|
||||
.version = "1.1.1",
|
||||
.license = "MIT",
|
||||
},
|
||||
.init = r2ai_init,
|
||||
|
||||
+4
-4
@@ -66,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 {
|
||||
@@ -256,7 +256,7 @@ 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_API char *r2ai(RCore *core, R2AIArgs args);
|
||||
|
||||
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args);
|
||||
|
||||
|
||||
+98
-74
@@ -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,7 +280,7 @@ 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;
|
||||
}
|
||||
@@ -300,12 +316,10 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
|
||||
max_backoff = 30; // Use default if invalid
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -330,7 +344,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
|
||||
|
||||
if (use_files) {
|
||||
// Write data to the temporary file
|
||||
if (!r_file_dump (temp_file, (const ut8*)data, strlen(data), 0)) {
|
||||
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) {
|
||||
@@ -344,7 +358,7 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
|
||||
|
||||
// Compose curl command
|
||||
RStrBuf *cmd = r_strbuf_new ("curl -s");
|
||||
|
||||
|
||||
// Add timeout
|
||||
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
|
||||
|
||||
@@ -364,34 +378,38 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
|
||||
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;
|
||||
sigaction (SIGINT, &old_action, 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) {
|
||||
@@ -412,10 +430,10 @@ static char *system_curl_post_file(RCore *core, const char *url, const char *hea
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -456,12 +474,10 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -474,7 +490,7 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
|
||||
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);
|
||||
|
||||
@@ -487,31 +503,35 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
|
||||
|
||||
// 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);
|
||||
sigaction (SIGINT, &old_action, 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) {
|
||||
@@ -532,10 +552,10 @@ static char *system_curl_get(const char *url, const char *headers[], int *code,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -564,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;
|
||||
@@ -581,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
|
||||
@@ -592,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) {
|
||||
@@ -635,7 +657,7 @@ 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;
|
||||
}
|
||||
@@ -669,12 +691,10 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -698,7 +718,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
|
||||
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';
|
||||
@@ -712,7 +732,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -809,7 +829,7 @@ static char *curl_http_get(const char *url, const char *headers[], int *code, in
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -840,12 +860,10 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -857,8 +875,10 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
|
||||
|
||||
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
|
||||
@@ -871,7 +891,9 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
|
||||
#endif
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
@@ -914,7 +936,7 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -922,12 +944,14 @@ static char *socket_http_get_with_interrupt(const char *url, const char *headers
|
||||
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) {
|
||||
backend = r_config_get (core->config, "r2ai.http.backend");
|
||||
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
|
||||
@@ -962,7 +986,7 @@ R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[],
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
+5
-9
@@ -1,3 +1,4 @@
|
||||
#include <string.h>
|
||||
#include "r2ai.h"
|
||||
|
||||
// Define the radare2 command tool
|
||||
@@ -322,11 +323,11 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **e
|
||||
command = *edited_command;
|
||||
}
|
||||
}
|
||||
R_LOG_DEBUG("Edited command: %s", *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", *edited_command);
|
||||
R2_PRINTF ("%s", red_command);
|
||||
@@ -350,11 +351,6 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **e
|
||||
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) {
|
||||
@@ -440,9 +436,9 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
}
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
char *payload_base64 = r_base64_encode_dyn ((const ut8*)payload, strlen (payload));
|
||||
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));
|
||||
char *payload_base64 = r_base64_encode_dyn ((const char *)payload, strlen (payload));
|
||||
#endif
|
||||
if (!payload_base64) {
|
||||
free (payload);
|
||||
|
||||
Reference in new issue
Block a user