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 | ||
|
|
2610309b58 | ||
|
|
0b81158d01 | ||
|
|
4bcd2c582d | ||
|
|
51aac5f348 | ||
|
|
a437938526 | ||
|
|
c6bba6c7dd | ||
|
|
3103df8210 | ||
|
|
b1fd4aab8e | ||
|
|
26ca088c32 | ||
|
|
710eb93bbe | ||
|
|
fb1f53a176 | ||
|
|
138fd5e7d3 | ||
|
|
bd040f9a48 | ||
|
|
1f3f04d74b | ||
|
|
a7cff9d970 | ||
|
|
a1de8828ac | ||
|
|
7a1d4668d6 | ||
|
|
e20f390878 | ||
|
|
be18fe91ad | ||
|
|
6df2a231eb | ||
|
|
668eee7807 | ||
|
|
111974071f | ||
|
|
b193caec61 | ||
|
|
8994415242 | ||
|
|
7e6953dce6 | ||
|
|
0bcd68c434 | ||
|
|
74e0b23ab0 | ||
|
|
1044bea8dd | ||
|
|
081e34a835 | ||
|
|
9f22b4c3c3 | ||
|
|
4fb8ab0f2e | ||
|
|
8e5b7e0152 | ||
|
|
e40c655013 | ||
|
|
0ebda776cf | ||
|
|
879592f22e | ||
|
|
fd3efa6c70 | ||
|
|
b420645c20 | ||
|
|
211e9af847 | ||
|
|
58e1eb6d14 | ||
|
|
c72fe14f19 | ||
|
|
10162f7302 | ||
|
|
949e22fdf6 | ||
|
|
2355c15cd5 | ||
|
|
c5db441a07 | ||
|
|
9853a99bcb | ||
|
|
4d685520b1 | ||
|
|
a20035fd31 | ||
|
|
733ebf9283 | ||
|
|
595c0ef2d6 | ||
|
|
c86b58a956 | ||
|
|
bd1f39b379 | ||
|
|
71bee067b3 | ||
|
|
713183d2c7 | ||
|
|
1f5406ae55 | ||
|
|
a9e5aa114b | ||
|
|
67c1a6d129 | ||
|
|
91bbd87c2d | ||
|
|
e1f8ddbff3 | ||
|
|
562aeb1ea5 | ||
|
|
2444f12acc | ||
|
|
d0d58670ec | ||
|
|
5eb8231ee1 | ||
|
|
10775172d8 | ||
|
|
5c39cc4c3f | ||
|
|
79ac83ff47 | ||
|
|
6869d9e259 | ||
|
|
dba793411f | ||
|
|
4f095b5e90 | ||
|
|
b4b73d3ceb | ||
|
|
c818e170a0 | ||
|
|
88955740d7 | ||
|
|
1859d64ca7 | ||
|
|
5af7f44498 | ||
|
|
0fb9ce8e8c | ||
|
|
2d444e7159 | ||
|
|
6b2e327fc3 | ||
|
|
6276fe6685 | ||
|
|
3c67d6693f | ||
|
|
35a2139380 | ||
|
|
594e2f4f20 | ||
|
|
5f281591b4 | ||
|
|
829ffa0133 | ||
|
|
2c1b41ad57 | ||
|
|
91ede7cc72 | ||
|
|
4b03d2458c | ||
|
|
57ab90b26a | ||
|
|
eba9d2ca4b | ||
|
|
165cd2bc46 | ||
|
|
c4f1ae900f | ||
|
|
e090521bd2 |
No files matched your search
@@ -1,49 +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: Setting up the Python virtual environment
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install pylint
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pip install .
|
||||
- name: Linting
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
make cilint
|
||||
- name: Running r2ai from the shell
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python -m r2ai.cli -h
|
||||
- name: Running r2ai from r2pipe
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
r2 -q -c '#!pipe python -m r2ai.cli -h' /bin/ls
|
||||
- name: Running r2ai from r2
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
r2 -q -i r2ai/plugin.py -c r2ai /bin/ls
|
||||
- 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:
|
||||
- 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,28 @@
|
||||
from transformers import AutoTokenizer
|
||||
import transformers
|
||||
import torch
|
||||
|
||||
model = "facebook/llm-compiler-13b"
|
||||
model = "QuantFactory/llm-compiler-7b-GGUF"
|
||||
# model = "file:///tmp/llm-compiler.gguf"
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(model)
|
||||
pipeline = transformers.pipeline(
|
||||
"text-generation",
|
||||
model=model,
|
||||
torch_dtype=torch.float16,
|
||||
device_map="auto",
|
||||
)
|
||||
|
||||
sequences = pipeline(
|
||||
'%3 = alloca i32, align 4',
|
||||
do_sample=True,
|
||||
top_k=10,
|
||||
temperature=0.1,
|
||||
top_p=0.95,
|
||||
num_return_sequences=1,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
max_length=200,
|
||||
)
|
||||
for seq in sequences:
|
||||
print(f"Result: {seq['generated_text']}")
|
||||
@@ -0,0 +1,249 @@
|
||||
import Foundation
|
||||
|
||||
// Token representation
|
||||
struct Token {
|
||||
var token: String
|
||||
var count: Int
|
||||
var df: Int
|
||||
}
|
||||
|
||||
// KD-Tree Node
|
||||
class KDNodeData {
|
||||
let point: [Float]
|
||||
let text: String
|
||||
var left: KDNodeData?
|
||||
var right: KDNodeData?
|
||||
let splitDim: Int
|
||||
|
||||
init(point: [Float], text: String, left: KDNodeData? = nil, right: KDNodeData? = nil, splitDim: Int) {
|
||||
self.point = point
|
||||
self.text = text
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.splitDim = splitDim
|
||||
}
|
||||
}
|
||||
|
||||
// Result for K-Nearest Neighbors
|
||||
struct KNNResult {
|
||||
let node: KDNodeData
|
||||
let distanceSq: Float
|
||||
}
|
||||
|
||||
class VectorDB {
|
||||
// Core database structures
|
||||
private let dimension: Int
|
||||
private var root: KDNodeData?
|
||||
private var tokens: [Token] = []
|
||||
private var totalDocs: Int = 0
|
||||
private var size: Int = 0
|
||||
|
||||
init(dimension: Int) {
|
||||
self.dimension = dimension
|
||||
}
|
||||
|
||||
// Validate token (similar to C implementation)
|
||||
private func isValidToken(_ token: String) -> Bool {
|
||||
let invalidTokens = ["pancake", "author", "radare2"]
|
||||
return !invalidTokens.contains(token)
|
||||
}
|
||||
|
||||
// Find a token in the token list
|
||||
private func findToken(_ tokens: [Token], _ token: String) -> Token? {
|
||||
return tokens.first { $0.token == token }
|
||||
}
|
||||
|
||||
// Compute embedding using TF-IDF approach
|
||||
private func computeEmbedding(_ text: String) -> [Float] {
|
||||
// Zero the embedding vector
|
||||
var embedding = [Float](repeating: 0.0, count: dimension)
|
||||
|
||||
// Tokenize and normalize
|
||||
let words = text
|
||||
.lowercased()
|
||||
.replacingOccurrences(of: "[^a-z0-9\\s]", with: " ", options: .regularExpression)
|
||||
.split(separator: " ")
|
||||
.map { String($0) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
// Local document token frequency
|
||||
var docTokens: [Token] = []
|
||||
for token in words {
|
||||
if let existingToken = findToken(docTokens, token) {
|
||||
let index = docTokens.firstIndex { $0.token == token }!
|
||||
docTokens[index].count += 1
|
||||
} else {
|
||||
docTokens.append(Token(token: token, count: 1, df: 0))
|
||||
}
|
||||
}
|
||||
|
||||
// Increment total documents
|
||||
totalDocs += 1
|
||||
|
||||
// Update global document frequencies
|
||||
for docToken in docTokens {
|
||||
if let globalToken = findToken(self.tokens, docToken.token) {
|
||||
if isValidToken(docToken.token) {
|
||||
let index = self.tokens.firstIndex { $0.token == docToken.token }!
|
||||
self.tokens[index].count += 1
|
||||
self.tokens[index].df += 1
|
||||
}
|
||||
} else {
|
||||
self.tokens.append(Token(token: docToken.token, count: docToken.count, df: 1))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute TF-IDF and update embedding
|
||||
for docToken in docTokens {
|
||||
// Compute term frequency
|
||||
let tf = 1 + log(Float(docToken.count))
|
||||
|
||||
// Find global token to get document frequency
|
||||
let dfValue = findToken(self.tokens, docToken.token)?.df ?? 1
|
||||
|
||||
// Compute inverse document frequency
|
||||
let idf = log(Float(totalDocs + 1) / Float(dfValue + 1)) + 1
|
||||
|
||||
// Compute weight
|
||||
let weight = tf * idf
|
||||
|
||||
// Hash-based index assignment
|
||||
let hash = simpleHash(docToken.token)
|
||||
let index = hash % dimension
|
||||
|
||||
// Add weighted value to embedding
|
||||
embedding[index] += weight
|
||||
}
|
||||
|
||||
// L2 Normalize the embedding
|
||||
return normalizeVector(embedding)
|
||||
}
|
||||
|
||||
// Simple string hash function
|
||||
private func simpleHash(_ str: String) -> Int {
|
||||
var hash = 0
|
||||
for char in str.unicodeScalars {
|
||||
hash = ((hash << 5) - hash) + Int(char.value)
|
||||
hash = hash & hash // Convert to 32-bit integer
|
||||
}
|
||||
return abs(hash)
|
||||
}
|
||||
|
||||
// Normalize vector to unit length
|
||||
private func normalizeVector(_ vec: [Float]) -> [Float] {
|
||||
let normSq = vec.reduce(0) { $0 + $1 * $1 }
|
||||
if normSq > 0 {
|
||||
let norm = sqrt(normSq)
|
||||
return vec.map { $0 / norm }
|
||||
}
|
||||
return vec
|
||||
}
|
||||
|
||||
// Compute squared Euclidean distance
|
||||
private func squaredDistance(_ a: [Float], _ b: [Float]) -> Float {
|
||||
guard a.count == b.count else { return Float.greatestFiniteMagnitude }
|
||||
return zip(a, b).reduce(0) { $0 + pow($1.0 - $1.1, 2) }
|
||||
}
|
||||
|
||||
// Recursive KD-Tree insertion
|
||||
private func insertRecursive(_ node: KDNodeData?, point: [Float], text: String, depth: Int) -> KDNodeData {
|
||||
guard let currentNode = node else {
|
||||
return KDNodeData(point: point, text: text, splitDim: depth % dimension)
|
||||
}
|
||||
|
||||
let axis = currentNode.splitDim
|
||||
if point[axis] < currentNode.point[axis] {
|
||||
currentNode.left = insertRecursive(currentNode.left, point: point, text: text, depth: depth + 1)
|
||||
} else {
|
||||
currentNode.right = insertRecursive(currentNode.right, point: point, text: text, depth: depth + 1)
|
||||
}
|
||||
|
||||
return currentNode
|
||||
}
|
||||
|
||||
// Insert a document into the vector database
|
||||
func insert(_ text: String) {
|
||||
guard !text.isEmpty else { return }
|
||||
|
||||
// Compute embedding
|
||||
let embedding = computeEmbedding(text)
|
||||
|
||||
// Insert into KD-Tree
|
||||
root = insertRecursive(root, point: embedding, text: text, depth: 0)
|
||||
size += 1
|
||||
}
|
||||
|
||||
// K-Nearest Neighbors search
|
||||
func query(_ text: String, k: Int) -> [String] {
|
||||
guard !text.isEmpty, root != nil else { return [] }
|
||||
|
||||
// Compute query embedding
|
||||
let queryEmbedding = computeEmbedding(text)
|
||||
|
||||
// Initialize result set
|
||||
var results: [KNNResult] = []
|
||||
|
||||
// Recursive KNN search
|
||||
func searchRecursive(_ node: KDNodeData?, query: [Float], depth: Int) {
|
||||
guard let node = node else { return }
|
||||
|
||||
// Compute distance and potentially insert into results
|
||||
let distSq = squaredDistance(query, node.point)
|
||||
|
||||
if results.count < k {
|
||||
results.append(KNNResult(node: node, distanceSq: distSq))
|
||||
// Maintain max-heap property
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
} else if distSq < results[0].distanceSq {
|
||||
results[0] = KNNResult(node: node, distanceSq: distSq)
|
||||
results.sort { $0.distanceSq > $1.distanceSq }
|
||||
}
|
||||
|
||||
// Decide which subtree to search first
|
||||
let axis = node.splitDim
|
||||
let diff = query[axis] - node.point[axis]
|
||||
let first = diff < 0 ? node.left : node.right
|
||||
let second = diff < 0 ? node.right : node.left
|
||||
|
||||
// Recursively search
|
||||
searchRecursive(first, query: query, depth: depth + 1)
|
||||
|
||||
// Potentially search the other subtree
|
||||
if results.count < k || diff * diff < results[0].distanceSq {
|
||||
searchRecursive(second, query: query, depth: depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
searchRecursive(root, query: queryEmbedding, depth: 0)
|
||||
|
||||
// Sort results and return texts
|
||||
return results
|
||||
.sorted { $0.distanceSq < $1.distanceSq }
|
||||
.map { $0.node.text }
|
||||
}
|
||||
|
||||
// Get total number of documents
|
||||
func getSize() -> Int {
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
func main() {
|
||||
// Create a vector database with 64-dimensional embeddings
|
||||
let db = VectorDB(dimension: 64)
|
||||
|
||||
// Insert some sample documents
|
||||
db.insert("The quick brown fox jumps over the lazy dog")
|
||||
db.insert("A quick brown fox is very quick")
|
||||
db.insert("The author of radare2 is name / named pancake")
|
||||
db.insert("The lazy dog sleeps all day")
|
||||
|
||||
// Query for similar documents
|
||||
let results = db.query("who is the author of radare2?", k: 2)
|
||||
print("Similar documents:", results)
|
||||
}
|
||||
|
||||
// Run example
|
||||
main()
|
||||
@@ -0,0 +1,81 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
PV=3.12
|
||||
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
|
||||
PYTHON?=python${PV}
|
||||
else
|
||||
PYTHON?=python3
|
||||
endif
|
||||
PIP=$(PYTHON) -m pip
|
||||
|
||||
ifeq ($(R2PM_BINDIR),)
|
||||
MISSING RADARE2
|
||||
endif
|
||||
|
||||
LINTED=r2ai/code_block.py
|
||||
LINTED+=r2ai/bubble.py
|
||||
LINTED+=r2ai/const.py
|
||||
LINTED+=r2ai/backend/kobaldcpp.py
|
||||
# LINTED+=r2ai/index.py
|
||||
# LINTED+=r2ai/voice.py
|
||||
# LINTED+=r2ai/anthropic.py
|
||||
r2ai-python py python r2aipy: venv
|
||||
@./r2ai.sh
|
||||
|
||||
large:
|
||||
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
|
||||
|
||||
all.old:
|
||||
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
|
||||
$(PYTHON) main.py || $(MAKE) deps
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv venv
|
||||
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
|
||||
|
||||
deps: venv
|
||||
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
|
||||
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
pip install --force-reinstall -U --no-cache-dir .
|
||||
clean:
|
||||
rm -rf venv
|
||||
rm -rf build
|
||||
find . -name "*.egg-info" -exec rm -rf {} +
|
||||
|
||||
user-install:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
|
||||
|
||||
install: user-install
|
||||
-mkdir -p /usr/local/share/man/man1/r2ai.1
|
||||
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
install-plugin user-install-plugin:
|
||||
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
-rm -f /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
user-uninstall-plugin uninstall-plugin:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
pub:
|
||||
$(PYTHON) -m build
|
||||
twine check dist/*
|
||||
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
|
||||
|
||||
pylint lint cilint:
|
||||
. venv/bin/activate ; pylint $(LINTED)
|
||||
|
||||
lintall:
|
||||
pylint *.py r2ai/*.py
|
||||
|
||||
.PHONY: lint lintall
|
||||
|
||||
deps-global:
|
||||
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# r2ai-python
|
||||
|
||||
This directory contains the original implementation of the AI support for radare2 written in Python.
|
||||
|
||||
It is considered _deprecated_, but it's still useful and aims to be maintained, we recommend using decai or the C rewrite as alternatives.
|
||||
|
||||
The Python implementation consists in a module can be used in 3 different ways:
|
||||
|
||||
* commandline repl using r2pipe to spawn or connect to remote radare2 instances
|
||||
* radare2 core plugin that is instantiated when placing it in the radare2 plugin's directory
|
||||
* Python API to be used for writing your own scripts
|
||||
|
||||
## API Providers
|
||||
|
||||
The Python implementation focused on supporting the LlamaCpp Python module, which is somehow heavy to load in many environments, but it also supports litellm which provides access to many external connectors like ollama, openai, anthropic, gemini, etc.
|
||||
|
||||
## Deprecation Reasons
|
||||
|
||||
There are several reasons why this implementation is considered deprecated and not recommended to use:
|
||||
|
||||
1) It's Python.
|
||||
|
||||
- Aka, it's slow, heavy loads in same process
|
||||
- Your system probably have multiple versions installed
|
||||
- The shell and radare2 python bindings are probably not the same
|
||||
- Required venv to be created (at least an extra 1GB)
|
||||
- Not typed, regressions and bugs appear at runtime
|
||||
|
||||
2) Goto 1
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python3 -m pip install .
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
### Selecting the model
|
||||
|
||||
- List all downloaded models: `-m`
|
||||
- Get a short list of models: `-MM`
|
||||
- Help: `-h`
|
||||
|
||||
**Example selecting a remote models:**
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m anthropic:claude-3-7-sonnet-20250219
|
||||
[r2ai:0x00006aa0]> -m openai:gpt-4
|
||||
```
|
||||
|
||||
**Example downloading a free local AI: Mistral 7B v0.2:**
|
||||
|
||||
Launch r2ai, select the model and ask a question. If the model isn't downloaded yet, r2ai will ask you which precise version to download.
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
```
|
||||
|
||||
Then ask your question, and r2ai will automatically download if needed:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> give me a short algorithm to test prime numbers
|
||||
Select TheBloke/Mistral-7B-Instruct-v0.2-GGUF model. See -M and -m flags
|
||||
[?] Quality (smaller is faster):
|
||||
> Small | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
Medium | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
Large | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
See More
|
||||
|
||||
[?] Quality (smaller is faster):
|
||||
> mistral-7b-instruct-v0.2.Q2_K.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_L.gguf | Size: 3.6 GB, Estimated RAM usage: 6.1 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_M.gguf | Size: 3.3 GB, Estimated RAM usage: 5.8 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_S.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q4_0.gguf | Size: 3.8 GB, Estimated RAM usage: 6.3 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_M.gguf | Size: 4.1 GB, Estimated RAM usage: 6.6 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_S.gguf | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
mistral-7b-instruct-v0.2.Q5_0.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_M.gguf | Size: 4.8 GB, Estimated RAM usage: 7.3 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_S.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q6_K.gguf | Size: 5.5 GB, Estimated RAM usage: 8.0 GB
|
||||
mistral-7b-instruct-v0.2.Q8_0.gguf | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
|
||||
[?] Use this model by default? ~/.r2ai.model:
|
||||
> Yes
|
||||
No
|
||||
|
||||
[?] Download to ~/.local/share/r2ai/models? (Y/n): Y
|
||||
```
|
||||
|
||||
**Example selecting a local model served by Ollama**
|
||||
|
||||
Download a model and make it available through Ollama:
|
||||
|
||||
```
|
||||
$ ollama ls
|
||||
NAME ID SIZE MODIFIED
|
||||
codegeex4:latest 867b8e81d038 5.5 GB 23 hours ago
|
||||
```
|
||||
|
||||
Use it from r2ai by prefixing its name with `ollama/`
|
||||
|
||||
```
|
||||
[r2ai:0x00002d30]> -m ollama/codegeex4:latest
|
||||
[r2ai:0x00002d30]> hi
|
||||
Hello! How can I assist you today?
|
||||
```
|
||||
|
||||
### Standard/Auto mode
|
||||
|
||||
The standard mode is invoked by directly asking the question.
|
||||
For the Auto mode, the question **must be prefixed** by `' ` (quote + space). The AI may instruct r2ai to run various commands. Those commands are run on *your host*, so you will be asked to review them before they run.
|
||||
|
||||
Example in "standard" mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> compute 4+5
|
||||
4 + 5 = 9
|
||||
[r2ai:0x00006aa0]> draw me a pancake in ASCII art
|
||||
Sure, here's a simple ASCII pancake:
|
||||
|
||||
_____
|
||||
( )
|
||||
( )
|
||||
-----
|
||||
```
|
||||
|
||||
Example in auto mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]>' Decompile the main
|
||||
[..]
|
||||
r2ai is going to execute the following command on the host
|
||||
Want to edit? (ENTER to validate) pdf @ fcn.000015d0
|
||||
This command will execute on this host: pdf @ fcn.000015d0. Agree? (y/N) y
|
||||
```
|
||||
|
||||
If you wish to edit the command, you can do it inline for short one line commands, or an editor will pop up.
|
||||
|
||||
### r2ai Configuration settings
|
||||
|
||||
List all settings with `-e`
|
||||
|
||||
| Key | Explanation |
|
||||
| ----------- | ------------------------------------- |
|
||||
| debug_level | All verbose messages for level 1. Default is 2 |
|
||||
| auto.max_runs | Maximum number of questions the AI is allowed to ask r2 in auto mode. |
|
||||
| auto.hide_tool_output | By default false, consequently output of r2cmd, run_python etc is shown. Set to `true` to hide those internal messages. |
|
||||
| chat.show_cost | Show the cost of each request to the AI if true |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "r2ai"
|
||||
dynamic = ["readme"]
|
||||
version = "0.9.4"
|
||||
version = "0.9.6"
|
||||
description = "Artificial intelligence tooling for radare2"
|
||||
|
||||
license = {text = "MIT License"}
|
||||
@@ -83,8 +83,8 @@ class ChatAuto:
|
||||
init_prompt = ""
|
||||
|
||||
if not self.llama_instance:
|
||||
|
||||
model_info = litellm.get_model_info(self.model)
|
||||
rmodel = re.sub(r'ft:([^:]+).*', r'\1', self.model)
|
||||
model_info = litellm.get_model_info(rmodel)
|
||||
self.max_tokens = min(self.max_tokens, model_info['max_tokens'])
|
||||
|
||||
if self.model.startswith('openai/o'):
|
||||
@@ -163,7 +163,7 @@ class Interpreter:
|
||||
self.env["chat.rawdog"] = "false"
|
||||
self.env["chat.reasoning_effort"] = "high"
|
||||
self.env["chat.stream"] = "true"
|
||||
self.env["chat.show_cost"] = "true"
|
||||
self.env["chat.show_cost"] = "false"
|
||||
self.env["chat.drop_params"] = "false"
|
||||
self.env["chat.openai_store"] = "false"
|
||||
self.env["auto.max_runs"] = "100"
|
||||
@@ -3,12 +3,17 @@ R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
R2_BINDIR=$(shell r2 -H R2_BINDIR)
|
||||
|
||||
install:
|
||||
# rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
# ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
mkdir -p $(R2_BINDIR)
|
||||
cp -f r2ai-server $(R2_BINDIR)/r2ai-server || echo Maybe use user-install instead?
|
||||
|
||||
uninstall:
|
||||
rm -f $(R2_BINDIR)/r2ai-server
|
||||
|
||||
user-install:
|
||||
mkdir -p $(R2PM_BINDIR)
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
ln -fs $(PWD)/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
|
||||
user-uninstall:
|
||||
@@ -0,0 +1,25 @@
|
||||
# r2ai-server
|
||||
|
||||
Start ollama, llamacpp, llamafile, r2ai, kobaldcpp and other language model webservers using a the same syntax for all of them to simplify its launching and setup.
|
||||
|
||||
* Use -l to list the implementations available
|
||||
|
||||
You can install more via r2pm
|
||||
|
||||
* Use -m to list or select the model. Those models can be absolute paths or model names when downloaded via r2ai, which uses the hugging face api
|
||||
|
||||
## Running r2ai-server
|
||||
|
||||
- Get usage: `r2pm -r r2ai-server`
|
||||
- List available servers: `r2pm -r r2ai-server -l`
|
||||
- List available models: `r2pm -r r2ai-server -m`
|
||||
|
||||
On Linux, models are stored in `~/.r2ai.models/`. File `~/.r2ai.model` lists the default model and other models.
|
||||
|
||||
**Example launching a local Mistral AI server:**
|
||||
|
||||
```
|
||||
$ r2pm -r r2ai-server -l r2ai -m mistral-7b-instruct-v0.2.Q2_K
|
||||
[12/13/24 10:35:22] INFO r2ai.server - INFO - [R2AI] Serving at port 8080 web.py:336
|
||||
```
|
||||
|
||||
@@ -1,93 +1,4 @@
|
||||
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
|
||||
PWD=$(shell pwd)
|
||||
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
|
||||
PV=3.12
|
||||
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
|
||||
PYTHON?=python${PV}
|
||||
else
|
||||
PYTHON?=python3
|
||||
endif
|
||||
PIP=$(PYTHON) -m pip
|
||||
|
||||
LINTED=r2ai/code_block.py
|
||||
LINTED+=r2ai/bubble.py
|
||||
LINTED+=r2ai/const.py
|
||||
LINTED+=r2ai/backend/kobaldcpp.py
|
||||
# LINTED+=r2ai/index.py
|
||||
# LINTED+=r2ai/voice.py
|
||||
# LINTED+=r2ai/anthropic.py
|
||||
|
||||
ifeq ($(R2PM_BINDIR),)
|
||||
FATAL ERROR
|
||||
endif
|
||||
|
||||
.PHONY: all all.old deps clean deps-global pub lint cilint
|
||||
.PHONY: install uninstall user-install user-uninstall
|
||||
|
||||
all: venv
|
||||
@./r2ai.sh
|
||||
|
||||
large:
|
||||
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
|
||||
|
||||
all.old:
|
||||
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
|
||||
$(PYTHON) main.py || $(MAKE) deps
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv venv
|
||||
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
|
||||
|
||||
deps: venv
|
||||
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
|
||||
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
pip install --force-reinstall -U --no-cache-dir .
|
||||
|
||||
clean:
|
||||
rm -rf venv
|
||||
rm -rf build
|
||||
find . -name "*.egg-info" -exec rm -rf {} +
|
||||
|
||||
mrproper:
|
||||
$(MAKE) clean
|
||||
|
||||
deps-global:
|
||||
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
|
||||
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
|
||||
|
||||
user-install:
|
||||
rm -f $(R2PM_BINDIR)/r2ai-server
|
||||
ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
|
||||
|
||||
install: user-install
|
||||
-mkdir -p /usr/local/share/man/man1/r2ai.1
|
||||
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
install-decai:
|
||||
$(MAKE) -C decai user-install
|
||||
|
||||
install-server:
|
||||
$(MAKE) -C r2ai-server user-install
|
||||
|
||||
install-plugin user-install-plugin:
|
||||
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
uninstall user-uninstall:
|
||||
rm -f $(R2PM_BINDIR)/r2ai
|
||||
-rm -f /usr/local/share/man/man1/r2ai.1
|
||||
|
||||
user-uninstall-plugin uninstall-plugin:
|
||||
rm -f $(R2_USER_PLUGINS)/r2ai.py
|
||||
|
||||
pub:
|
||||
$(PYTHON) -m build
|
||||
twine check dist/*
|
||||
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
|
||||
|
||||
cilint:
|
||||
pylint $(LINTED)
|
||||
|
||||
lint:
|
||||
pylint *.py r2ai/*.py
|
||||
TARGETS=all clean mrproper user-install install user-uninstall uninstall
|
||||
$(TARGETS):
|
||||
$(MAKE) -C src $@
|
||||
.PHONY: $(TARGETS)
|
||||
@@ -1,100 +1,70 @@
|
||||
```
|
||||
,______ .______ .______ ,___
|
||||
: __ \ \____ |: \ : __|
|
||||
| \____|/ ____|| _,_ || : |
|
||||
| : \ \ . || : || |
|
||||
| |___\ \__:__||___| || |
|
||||
|___| : |___||___|
|
||||
*
|
||||
# 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/r2clippy.jpg">
|
||||
</p>
|
||||
[](https://github.com/radareorg/radare2)
|
||||
|
||||
## Components
|
||||
|
||||
R2AI is structured into four independent components:
|
||||
This repository contains two plugins for radare2:
|
||||
|
||||
* r2ai (python cli tool)
|
||||
* r2-like repl using r2pipe to comunicate with r2
|
||||
* supports auto solving mode
|
||||
* client and server openapi protocol
|
||||
* download and manage models from huggingface
|
||||
* decai (r2js plugin focus on decompilation)
|
||||
* adds 'decai' command to the r2 shell
|
||||
* talks to local or remote services with curl
|
||||
* focus on decompilation
|
||||
* *r2ai-plugin*
|
||||
* Native plugin written in C
|
||||
* adds r2ai command inside r2
|
||||
* 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
|
||||
|
||||
* **r2ai** - native plugin for radare2
|
||||
* **decai** - r2js plugin with special focus on decompilation
|
||||
|
||||
If you are looking to use radare2 with other agents via MCP:
|
||||
|
||||
* **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
|
||||
|
||||
### From r2pm
|
||||
The recommended way to install any of the r2ai components is via r2pm:
|
||||
|
||||
Install the various components via `r2pm`:
|
||||
|
||||
- `r2pm -ci r2ai`
|
||||
- `r2pm -ci r2ai-plugin`
|
||||
- `r2pm -ci decai`
|
||||
- `r2pm -ci r2ai-server`
|
||||
|
||||
### From sources
|
||||
|
||||
Running `make` will setup a python virtual environment in the current directory installing all the necessary dependencies and will get into a shell to run r2ai.
|
||||
|
||||
The installation is now splitted into two different targets:
|
||||
|
||||
* `make install` will place a symlink in `$BINDIR/r2ai`
|
||||
* `make install-decai` will install the decai r2js decompiler plugin
|
||||
* `make install-server` will install the r2ai-server
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/radareorg/r2ai
|
||||
cd r2ai
|
||||
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
|
||||
python3 -m pip install .
|
||||
python3 main.py
|
||||
```console
|
||||
$ r2pm -Uci r2ai
|
||||
$ r2pm -Uci decai
|
||||
```
|
||||
|
||||
## Running r2ai
|
||||
## Using r2ai
|
||||
|
||||
### Launch r2ai
|
||||
- Adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai`
|
||||
- You can also run the wrapper in $PATH: `r2pm -r r2ai`
|
||||
|
||||
- If you installed via r2pm, you can execute it like this: `r2pm -r r2ai`
|
||||
- Otherwise, `./r2ai.sh [/absolute/path/to/binary]`
|
||||
Drop your API keys in environment variables or files in your home:
|
||||
|
||||
If you have an **API key**, put it in the adequate file:
|
||||
```console
|
||||
$ cat ~/.r2ai.anthropic-key
|
||||
sk-ant-api03-CENSORED
|
||||
$ export OPENAI_API_KEY=sk-proj-6rlSPS-zN1v...
|
||||
```
|
||||
|
||||
| AI | API key |
|
||||
| --------- | -------------------------- |
|
||||
@@ -103,289 +73,24 @@ If you have an **API key**, put it in the adequate file:
|
||||
| Anthropic | `$HOME/.r2ai.anthropic-key` |
|
||||
| Mistral | `$HOME/.r2ai.mistral-key` |
|
||||
|
||||
Example using an Anthropic API key:
|
||||
|
||||
## Saving settings
|
||||
|
||||
You may customize and save your configuration settings using your OS's default settings file (e.g `~/.radare2rc` on Linux).
|
||||
For example, the following configuration sets Claude 3.7 by default, with max output tokens to 64000.
|
||||
|
||||
```
|
||||
$ cat ~/.r2ai.anthropic-key
|
||||
sk-ant-api03-CENSORED
|
||||
r2ai -e api=anthropic
|
||||
r2ai -e model=claude-3-7-sonnet-20250219
|
||||
r2ai -e max_tokens=64000
|
||||
```
|
||||
|
||||
### Selecting the model
|
||||
|
||||
- List all downloaded models: `-m`
|
||||
- Get a short list of models: `-MM`
|
||||
- Help: `-h`
|
||||
|
||||
**Example selecting a remote models:**
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m anthropic:claude-3-7-sonnet-20250219
|
||||
[r2ai:0x00006aa0]> -m openai:gpt-4
|
||||
```
|
||||
|
||||
**Example downloading a free local AI: Mistral 7B v0.2:**
|
||||
|
||||
Launch r2ai, select the model and ask a question. If the model isn't downloaded yet, r2ai will ask you which precise version to download.
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
|
||||
```
|
||||
|
||||
Then ask your question, and r2ai will automatically download if needed:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> give me a short algorithm to test prime numbers
|
||||
Select TheBloke/Mistral-7B-Instruct-v0.2-GGUF model. See -M and -m flags
|
||||
[?] Quality (smaller is faster):
|
||||
> Small | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
Medium | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
Large | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
See More
|
||||
|
||||
[?] Quality (smaller is faster):
|
||||
> mistral-7b-instruct-v0.2.Q2_K.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_L.gguf | Size: 3.6 GB, Estimated RAM usage: 6.1 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_M.gguf | Size: 3.3 GB, Estimated RAM usage: 5.8 GB
|
||||
mistral-7b-instruct-v0.2.Q3_K_S.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
|
||||
mistral-7b-instruct-v0.2.Q4_0.gguf | Size: 3.8 GB, Estimated RAM usage: 6.3 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_M.gguf | Size: 4.1 GB, Estimated RAM usage: 6.6 GB
|
||||
mistral-7b-instruct-v0.2.Q4_K_S.gguf | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
|
||||
mistral-7b-instruct-v0.2.Q5_0.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_M.gguf | Size: 4.8 GB, Estimated RAM usage: 7.3 GB
|
||||
mistral-7b-instruct-v0.2.Q5_K_S.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
|
||||
mistral-7b-instruct-v0.2.Q6_K.gguf | Size: 5.5 GB, Estimated RAM usage: 8.0 GB
|
||||
mistral-7b-instruct-v0.2.Q8_0.gguf | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
|
||||
|
||||
[?] Use this model by default? ~/.r2ai.model:
|
||||
> Yes
|
||||
No
|
||||
|
||||
[?] Download to ~/.local/share/r2ai/models? (Y/n): Y
|
||||
```
|
||||
|
||||
**Example selecting a local model served by Ollama**
|
||||
|
||||
Download a model and make it available through Ollama:
|
||||
|
||||
```
|
||||
$ ollama ls
|
||||
NAME ID SIZE MODIFIED
|
||||
codegeex4:latest 867b8e81d038 5.5 GB 23 hours ago
|
||||
```
|
||||
|
||||
Use it from r2ai by prefixing its name with `ollama/`
|
||||
|
||||
```
|
||||
[r2ai:0x00002d30]> -m ollama/codegeex4:latest
|
||||
[r2ai:0x00002d30]> hi
|
||||
Hello! How can I assist you today?
|
||||
```
|
||||
|
||||
### Standard/Auto mode
|
||||
|
||||
The standard mode is invoked by directly asking the question.
|
||||
For the Auto mode, the question **must be prefixed** by `' ` (quote + space). The AI may instruct r2ai to run various commands. Those commands are run on *your host*, so you will be asked to review them before they run.
|
||||
|
||||
Example in "standard" mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]> compute 4+5
|
||||
4 + 5 = 9
|
||||
[r2ai:0x00006aa0]> draw me a pancake in ASCII art
|
||||
Sure, here's a simple ASCII pancake:
|
||||
|
||||
_____
|
||||
( )
|
||||
( )
|
||||
-----
|
||||
```
|
||||
|
||||
Example in auto mode:
|
||||
|
||||
```
|
||||
[r2ai:0x00006aa0]>' Decompile the main
|
||||
[..]
|
||||
r2ai is going to execute the following command on the host
|
||||
Want to edit? (ENTER to validate) pdf @ fcn.000015d0
|
||||
This command will execute on this host: pdf @ fcn.000015d0. Agree? (y/N) y
|
||||
```
|
||||
|
||||
If you wish to edit the command, you can do it inline for short one line commands, or an editor will pop up.
|
||||
|
||||
### r2ai Configuration settings
|
||||
|
||||
List all settings with `-e`
|
||||
|
||||
| Key | Explanation |
|
||||
| ----------- | ------------------------------------- |
|
||||
| debug_level | All verbose messages for level 1. Default is 2 |
|
||||
| auto.max_runs | Maximum number of questions the AI is allowed to ask r2 in auto mode. |
|
||||
| auto.hide_tool_output | By default false, consequently output of r2cmd, run_python etc is shown. Set to `true` to hide those internal messages. |
|
||||
| chat.show_cost | Show the cost of each request to the AI if true |
|
||||
|
||||
|
||||
## Running r2ai-server
|
||||
|
||||
- Get usage: `r2pm -r r2ai-server`
|
||||
- List available servers: `r2pm -r r2ai-server -l`
|
||||
- List available models: `r2pm -r r2ai-server -m`
|
||||
|
||||
On Linux, models are stored in `~/.r2ai.models/`. File `~/.r2ai.model` lists the default model and other models.
|
||||
|
||||
**Example launching a local Mistral AI server:**
|
||||
|
||||
```
|
||||
$ r2pm -r r2ai-server -l r2ai -m mistral-7b-instruct-v0.2.Q2_K
|
||||
[12/13/24 10:35:22] INFO r2ai.server - INFO - [R2AI] Serving at port 8080 web.py:336
|
||||
```
|
||||
|
||||
## Running decai
|
||||
|
||||
Decai is used from `r2` (e.g `r2 ./mybinary`). Get help with `decai -h`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -h
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -a [query] - solve query with auto mode
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
...
|
||||
```
|
||||
|
||||
List configuration variables with `decai -e`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO 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:'
|
||||
decai -e ctxfile=
|
||||
...
|
||||
```
|
||||
|
||||
List possible APIs to discuss with AI: `decai -e api=?`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=?
|
||||
r2ai
|
||||
claude
|
||||
openapi
|
||||
...
|
||||
```
|
||||
|
||||
### Example using a local model and ollama
|
||||
|
||||
For example, if Ollama serves model codegeex4:latest (`ollama ls`), set decai API as `ollama` and model `codegeex4:latest`.
|
||||
|
||||
```
|
||||
[0x00002d30]> decai -e api=ollama
|
||||
[0x00002d30]> decai -e model=codegeex4:latest
|
||||
[0x00002d30]> decai -q Explain what forkpty does in 2 lines
|
||||
The `forkpty` function creates a new process with a pseudo-terminal, allowing the parent to interact with the child via standard input/output/err and controlling its terminal.
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Example using a local Mistral model and r2ai-server
|
||||
|
||||
For example, assuming we have a *local* Mistral AI server running on port 8080 with `r2ai-server`, we can decompile a given function with `decai -d`.
|
||||
The server shows it received the question:
|
||||
|
||||
```
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -R
|
||||
127.0.0.1 - - [13/Dec/2024 10:40:49] "GET /cmd/-R HTTP/1.1" 200 -
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -i /tmp/.pdc.txt Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change goto into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like string:. Transform this pseudocode into C
|
||||
```
|
||||
|
||||
### Example using a Mistral API key
|
||||
|
||||
Put the API key in `~/.r2ai.mistral-key`.
|
||||
|
||||
```
|
||||
[0x000010d0]> decai -e api=mistral
|
||||
[0x000010d0]> decai -d main
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char password[40];
|
||||
char input[40];
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
### Example with ChatGPT 4
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=openai
|
||||
[0x00406cac]> decai -d
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void daemonize() {
|
||||
daemon(1, 0);
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
* There's [a chapter](https://book.rada.re/plugins/r2ai.html) in the official r2book
|
||||
* Cryptax on [lmstudio+gptoss](https://cryptax.medium.com/r2ai-with-lmstudio-and-gpt-oss-08efa5ea2476) blog post
|
||||
* Malware analysis [with r2ai](https://github.com/cryptax/talks/blob/master/BSidesKristiansand-2025/r2ai.pdf) by Cryptax
|
||||
|
||||
## Videos
|
||||
|
||||
- https://infosec.exchange/@radareorg/111946255058894583
|
||||
|
||||
|
||||
## Development/Testing
|
||||
|
||||
Just run `make`
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
* add "undo" command to drop the last message
|
||||
* dump / restore conversational states (see -L command)
|
||||
* Implement `~`, `|` and `>` and other r2shell features
|
||||
|
||||
|
||||
## Messages API
|
||||
|
||||
The r2ai project includes a new API for managing messages and tool calls in conversations with AI models. This API is implemented in `messages.c` and `messages.h`.
|
||||
|
||||
### Key Functions
|
||||
|
||||
- `r2ai_messages_new()`: Create a new messages array
|
||||
- `r2ai_messages_free(msgs)`: Free a messages array and all associated data
|
||||
- `r2ai_messages_add(msgs, role, content, tool_call_id)`: Add a new message
|
||||
- `r2ai_messages_add_tool_call(msgs, name, arguments, id)`: Add a tool call to the last message
|
||||
- `r2ai_messages_last(msgs)`: Get the last message in the array
|
||||
- `r2ai_messages_parse_json(msgs, json)`: Parse a JSON response into messages
|
||||
- `r2ai_messages_clear(msgs)`: Clear all messages
|
||||
|
||||
### Example Usage
|
||||
|
||||
```c
|
||||
// Create a new messages array
|
||||
R2AI_Messages *msgs = r2ai_messages_new();
|
||||
|
||||
// Add a user message
|
||||
r2ai_messages_add(msgs, "user", "What is the disassembly of function main?", NULL);
|
||||
|
||||
// Add an assistant message with a tool call
|
||||
R2AI_Message *msg = r2ai_messages_add(msgs, "assistant", NULL, NULL);
|
||||
r2ai_messages_add_tool_call(msgs, "r2cmd", "{\"command\":\"pdf@main\"}", "tool-123");
|
||||
|
||||
// Free all resources when done
|
||||
r2ai_messages_free(msgs);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
vim src/r2ai.c
|
||||
vim VERSION
|
||||
@@ -4,9 +4,15 @@ SPD=$(shell r2 -H R2_LIBR_PLUGINS)
|
||||
BF=a.out
|
||||
|
||||
all:
|
||||
@echo "Run: make user-install"
|
||||
|
||||
test:
|
||||
@echo 'You can now use 'decai' command'
|
||||
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
|
||||
|
||||
fmt indent:
|
||||
deno fmt
|
||||
|
||||
user-install:
|
||||
mkdir -p "$(PD)"
|
||||
cp decai.r2.js $(PD)/decai.r2.js
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
The AI based Decompiler plugin for radare2
|
||||
|
||||
* Written in plain Javascript
|
||||
* No dependencies than radare2 and curl in PATH
|
||||
* Uses local ollama by default, but also r2ai-server
|
||||
* Use services like Anthropic, OpenAI, HF, XAI, DeepSeek, ..
|
||||
- Written in plain Javascript
|
||||
- No dependencies than radare2 and curl in PATH
|
||||
- Uses local ollama by default, but also r2ai-server
|
||||
- Use services like Anthropic, OpenAI, HF, XAI, DeepSeek, ..
|
||||
|
||||
Features
|
||||
|
||||
* Auto mode with function calling with ANY model
|
||||
* Uses the r2 pseudo decompiler by default, supports any other
|
||||
* Explain purpose and auto-document functions
|
||||
* Recursive decompilation to inline stubs
|
||||
* Autoname functions and perform type propagation
|
||||
* Find vulnerabilities, guide you and write exploits
|
||||
* Choose any natural language (not just English)
|
||||
* Choose output programming language (not just C)
|
||||
* Chain queries to manually fine tune the results
|
||||
* Customize decompilation prompt at any time
|
||||
- Auto mode with function calling with ANY model
|
||||
- Uses the r2 pseudo decompiler by default, supports any other
|
||||
- Explain purpose and auto-document functions
|
||||
- Recursive decompilation to inline stubs
|
||||
- Autoname functions and perform type propagation
|
||||
- Find vulnerabilities, guide you and write exploits
|
||||
- Choose any natural language (not just English)
|
||||
- Choose output programming language (not just C)
|
||||
- Chain queries to manually fine tune the results
|
||||
- Customize decompilation prompt at any time
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -68,10 +68,60 @@ decai -e model=
|
||||
decai -e maxinputtokens=-1
|
||||
```
|
||||
|
||||
## Running decai
|
||||
|
||||
Decai is used from `r2` (e.g `r2 ./mybinary`). Get help with `decai -h`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -h
|
||||
Usage: decai (-h) ...
|
||||
decai -H - help setting up r2ai
|
||||
decai -a [query] - solve query with auto mode
|
||||
decai -d [f1 ..] - decompile given functions
|
||||
decai -dr - decompile function and its called ones (recursive)
|
||||
decai -dd [..] - same as above, but ignoring cache
|
||||
decai -dD [query]- decompile current function with given extra query
|
||||
...
|
||||
```
|
||||
|
||||
List configuration variables with `decai -e`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e
|
||||
decai -e api=ollama
|
||||
decai -e host=http://localhost
|
||||
decai -e port=11434
|
||||
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO 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:'
|
||||
decai -e ctxfile=
|
||||
...
|
||||
```
|
||||
|
||||
List possible APIs to discuss with AI: `decai -e api=?`:
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=?
|
||||
r2ai
|
||||
claude
|
||||
openapi
|
||||
...
|
||||
```
|
||||
|
||||
### Example using a local model and ollama
|
||||
|
||||
For example, if Ollama serves model codegeex4:latest (`ollama ls`), set decai
|
||||
API as `ollama` and model `codegeex4:latest`.
|
||||
|
||||
```
|
||||
[0x00002d30]> decai -e api=ollama
|
||||
[0x00002d30]> decai -e model=codegeex4:latest
|
||||
[0x00002d30]> decai -q Explain what forkpty does in 2 lines
|
||||
The `forkpty` function creates a new process with a pseudo-terminal, allowing the parent to interact with the child via standard input/output/err and controlling its terminal.
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See [https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
|
||||
|
||||
See
|
||||
[https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
|
||||
|
||||
```c
|
||||
$ cat stack-overflow/bug.c
|
||||
@@ -100,4 +150,50 @@ int main(int argc, char **argv, char **envp) {
|
||||
}
|
||||
```
|
||||
|
||||
--pancake
|
||||
### Example using a local Mistral model and r2ai-server
|
||||
|
||||
For example, assuming we have a _local_ Mistral AI server running on port 8080
|
||||
with `r2ai-server`, we can decompile a given function with `decai -d`. The
|
||||
server shows it received the question:
|
||||
|
||||
```
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -R
|
||||
127.0.0.1 - - [13/Dec/2024 10:40:49] "GET /cmd/-R HTTP/1.1" 200 -
|
||||
GET
|
||||
CUSTOM
|
||||
RUNLINE: -i /tmp/.pdc.txt Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change goto into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like string:. Transform this pseudocode into C
|
||||
```
|
||||
|
||||
### Example using a Mistral API key
|
||||
|
||||
Put the API key in `~/.r2ai.mistral-key`.
|
||||
|
||||
````
|
||||
[0x000010d0]> decai -e api=mistral
|
||||
[0x000010d0]> decai -d main
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char password[40];
|
||||
char input[40];
|
||||
...
|
||||
````
|
||||
|
||||
### Example with ChatGPT 4
|
||||
|
||||
```
|
||||
[0x00406cac]> decai -e api=openai
|
||||
[0x00406cac]> decai -d
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void daemonize() {
|
||||
daemon(1, 0);
|
||||
}
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
FROM ubuntu:24.04
|
||||
WORKDIR /root/
|
||||
RUN apt update && apt install -y build-essential
|
||||
RUN apt install -y git curl pkg-config
|
||||
RUN apt install -y python3.12
|
||||
RUN apt install -y python3.12-venv
|
||||
RUN apt install -y zlib1g zlib1g-dev
|
||||
RUN apt install -y unzip neovim
|
||||
RUN apt install -y ninja-build meson
|
||||
|
||||
# install radare
|
||||
RUN git clone https://github.com/radareorg/radare2.git
|
||||
WORKDIR radare2/
|
||||
RUN sys/install.sh
|
||||
|
||||
# install plugins
|
||||
WORKDIR /root/
|
||||
RUN r2pm -U
|
||||
#RUN r2pm -ci r2ghidra r2ghidra-sleigh # slow and optional
|
||||
RUN r2pm -ci r2ai-server r2ai-plugin # r2ai-py is deprecated
|
||||
RUN r2pm -ci decai r2dec
|
||||
|
||||
# r2 tunning
|
||||
RUN echo 'e io.unalloc=true' >> /root/.radare2rc
|
||||
RUN echo 'e cfg.fortunes=false' >> /root/.radare2rc
|
||||
RUN echo 'e emu.str=true' >> /root/.radare2rc
|
||||
RUN echo 'e bin.relocs.apply=true' >> /root/.radare2rc
|
||||
RUN echo 'e dir.tmp=/tmp/' >> /root/.radare2rc
|
||||
RUN echo 'e scr.prompt.flag=1' >> /root/.radare2rc
|
||||
RUN echo 'e cfg.editor=nvim' >> /root/.radare2rc
|
||||
RUN echo 'decai -e deterministic=true' >> /root/.radare2rc
|
||||
RUN echo 'decai -e lang=Python' >> /root/.radare2rc
|
||||
RUN echo 'decai -e cmds=pdd,pdg' >> /root/.radare2rc
|
||||
RUN echo 'decai -e prompt=..' >> /root/.radare2rc
|
||||
RUN echo 'decai -e api=ollama' >> /root/.radare2rc
|
||||
RUN echo 'decai -e model=qwen2.5-coder:latest' >> /root/.radare2rc
|
||||
|
||||
|
||||
# Uncomment to have ollama inside the docker (will not use gpu)
|
||||
#RUN curl -fsSL https://ollama.com/install.sh | sh
|
||||
#RUN \
|
||||
# echo '#!/bin/bash'; \
|
||||
# echo 'ollama serve &'; \
|
||||
# echo 'sleep 5'; \
|
||||
# echo 'ollama pull qwen2.5-coder:latest'; >> /root/ollama.run
|
||||
#
|
||||
#RUN chmod +x /root/ollama.run
|
||||
#RUN /root/ollama.run
|
||||
|
||||
|
||||
@@ -1,86 +1,163 @@
|
||||
the author of radare2 is named pancake, also known as Sergi Alvarez
|
||||
reopen the file in read-write mode use the `oo+` command
|
||||
pancake was born in Catalonia, Sergi Alvarez, r2 author is from Barcelona
|
||||
to run a script use the -i commandline flag. Some cmdline flags are also accessible from the r2 shell
|
||||
you can also use `. ...` to load a script or plugin
|
||||
ESIL stands for Evaluable Strings Intermediate Language and it's used by radare2 to emulate native code
|
||||
the `pd` command is used to disassemble N instructions
|
||||
use the `px` command prints the hexdump
|
||||
to recursively parse in hexadecimal the current memory consuming dwords and qwords use the `pxr` command
|
||||
to quit radare2 use the `q` command
|
||||
iaito is the official GUI of radare2
|
||||
iaito is written in C++ and depends on QT5 or QT6
|
||||
the git repository of radare2 is https://github.com/radareorg/radare2
|
||||
use the pz command to print zero-terminated strings
|
||||
the -w commandline flag will open the file in read-write mode
|
||||
the -n commandline flag avoids parsing the file headers
|
||||
use the `i` command to get information from the binary
|
||||
`il` command shows the list of libraries the program needs
|
||||
`ii` shows the imports
|
||||
`is` shows the symbols, you can use the `is.` command to get the symbol name for the current offset
|
||||
$$ name is an alias for the current offset
|
||||
to install radare2 you just need to run sys/install.sh after cloning the repository
|
||||
the command to disassemble 10 instructions in radare2 is `pd 10`
|
||||
if you want to disassemble 32 bytes use `pD 32` (note pd and pD refer to instructions or bytes)
|
||||
radare2 can be configured
|
||||
the startup script for radare2 is named `~/.radare2rc`. This file is used also to define the initial configuration options
|
||||
to change any configuration use the `e` command or the `-e` commandline flag
|
||||
cli and cmdline are aliases for commandline
|
||||
to analyze the program use the aa command, note that there's also aaa, aaaa and aaaaa, which perform more and deeper analysis actions
|
||||
to seek, move or change the current in radare2 use the `s` command
|
||||
relative seeks can be done with `s+` and `s-` appending the number
|
||||
you can set the default configuration options in the `~/.radare2rc` file
|
||||
to disassemble the function you can use the `pdf` or `pdr` commands
|
||||
to get the control flow graph of the current function in ascii art use the `agf` command
|
||||
the ?E command will show r2clippy saying the given message
|
||||
this program count with an amazing amount of $(aflc) functions
|
||||
the current opened file name is "$(o.)"
|
||||
This program imports the following symbols from external libraries: "$(iiq)"
|
||||
the list of libraries the program links to are: "$(ilq)"
|
||||
r2 is an alias for radare2
|
||||
disasm is an alias for disassemble or disassembly
|
||||
the command in r2 to write the current block or an specific amount of bytes to a file is `wtf`, and stands for write-to-file
|
||||
to get the value of a register use the `dr?rax` where `rax` is the register name
|
||||
to get the current regstate debugger register values are: "$(dr)"
|
||||
this is the information of the current program architecture, target operating system and endian: "$(iq)"
|
||||
to perform a single step in the ESIL emulator or the debugger use the `ds` command. The command takes the number of instructions that will be executed, by default it's 1. For example `ds 32` is the same as `32ds`
|
||||
radare2 commands can be prefixed with a number, in that case the command after the decimals is executed many times
|
||||
if the command starts with "0x", it is the same as seeking there
|
||||
the current value of the program counter $(dr?PC)
|
||||
to display the hexpair string of the bytes in the current offset use `p8`
|
||||
the first 32 bytes in the current offset are: $(p8 32)
|
||||
to step into 10 instructions use the `ds 10` command
|
||||
step over won't go into calls, it will execute until is useful when you don't want to go into calls
|
||||
radare started in 2006 as simple, but advanced hexadecimal editor
|
||||
r2frida is a plugin for radare2 that to instrument and trace function calls on live processes using Frida
|
||||
r2frida commands start with the `:` character. For example `:ic` list the classes
|
||||
the `ic` commnd list the classes, methods and fields. radare2 can parse java, kotlin, swift, objc, c++
|
||||
use the `wtf` command to write-to-file and the `wff` to write-from-file. think like dump and slurp
|
||||
r2pipe example script: ```js\nconst msg = r2.cmd("?E Hello World");\nconsole.log(msg);\n```
|
||||
the r2pipe js API provides a .cmd() and .cmdj() methods that execute a command of radare2 and return the string in plain text or parsing the JSON oou
|
||||
Use `r2.cmdj()` when you want to parse the output of the command as if it was JSON. This is useful to get structured information from r2. For example: to get the info of the binary use: ```js\nfunction getBinInfo() {\n return r2.cmdj("ij")\n}\n```` neer2pipe js API provides a .cmd() and .cmdj() methods that execute a command of radare2 and return the string in plain text or parsing the JSON oou
|
||||
the radare project started the year 2006 by pancake (real name is "Sergi Alvarez") and it was designed as a tool for forensics analysis
|
||||
the radare2 project started as a rewrite of radare in 2009 in order to resolve all the design limitations of the original code
|
||||
Sergi Alvarez was born in the city of Barcelona, so we can say that pancake is from Catalonia
|
||||
the first purpose of radare was to recover some deleted files from an HFS partition on an old macppc, the tool accessed the disk in raw and searched for known strings and dumped the results as separte files to disk
|
||||
the real name of pancake is Sergi Alvarez, he is a Catalan hacker and free software activist who developed many other opensource software, like r2frida, applesign, the 0xFFFF Nokia Flasher, ACR the autotools replacement, and many more
|
||||
the author of radius2 is Alkali. radius2 is a fast binary emulation and symbolic execution written in Rust and based on radare2's ESIL
|
||||
the `eco` command is used to change the color theme in radare2
|
||||
to list all the available color themes run `eco` without any argument. But you can change the current one by passing it as argument
|
||||
to edit the radare2rc file use the `ed` command. This script will be executed at startup time and can be ignored by using the `r2 -n` flag
|
||||
ACR stands for AutoConfReplacement, and it is a minimalistic implementation of the GNU autoconf tool developed by pancake and used in radare2 as well as in other projects
|
||||
to perform a step into the next instruction in the radare2 debugger use the `ds` command. To step over use `dso`
|
||||
the command f~sym lists flags containing “sym”
|
||||
set -e cfg.bigendian=true to display values in big endian when using (for example) the pv4 command
|
||||
scripts specific to files or projects can be created by placing a .r2 file in the binary’s directory
|
||||
to identify the architecture of a binary, you can use the iI command to inspect headers and the ie command to list entry points
|
||||
the V key enters visual mode, while q exits it, allowing for interactive disassembly navigation
|
||||
the `fs imports; f` command lists all flags from the 'imports' flagspace, which should be pointing to the functions imported in all the binaries loaded
|
||||
use `ood` to reload the current binary in debugger mode (same as starting with 'r2 -d')
|
||||
the `afl` command lists all functions detected in the binary
|
||||
the iz command extracts strings from the binary
|
||||
the `-e anal.hasnext=true` setting enables detection of consecutive functions when analyzing code
|
||||
the `pdc` command decompiles the current function using pseudocode
|
||||
use the ao command to view the current instruction with detailed operand information
|
||||
to rename a function, use the command `afn new_name @ address`
|
||||
the iS command lists all sections in the binary, along with their permissions and offsets
|
||||
radare2's author is Sergi Alvarez, also known as pancake.
|
||||
pancake is from Catalonia, Barcelona.
|
||||
radare2 originated in 2006 as a forensic tool for HFS+ recovery.
|
||||
The initial radare aimed for portability, 64bit offset handling, and hex pattern searching.
|
||||
radare2 (r2) was born in 2009 as a refactored, modular version of radare1.
|
||||
radare2 follows UNIX principles: "everything is a file", small interacting programs.
|
||||
The r2pipe API allows accessing radare2 via pipes from any language.
|
||||
radare2 development is managed on GitHub and discussed on Telegram/Discord.
|
||||
r2con is the annual radare2 community conference, usually held in Barcelona.
|
||||
what is ESIL? the meaning is Evaluable Strings Intermediate Language.
|
||||
ESIL is used by radare2 to emulate native code.
|
||||
radare2 treats executables, disk images, memory dumps, and processes as binary files.
|
||||
Core radare2 features include hex editing, disassembly, debugging, patching, and scripting.
|
||||
ACR stands for AutoConfReplacement, a minimalistic autoconf tool used by radare2.
|
||||
The radare2 GitHub repository is located at radareorg/radare2.
|
||||
To install radare2 from source, clone the repository and run `sys/install.sh`.
|
||||
Build issues can sometimes be resolved by cleaning plugin/cache directories (`~/.local/share/radare2/`).
|
||||
Use `R2_DEBUG=1` environment variable for startup debugging information.
|
||||
`make purge` can help remove old radare2 installations.
|
||||
Use `r2 <file>` to open a file in radare2.
|
||||
Use `r2 -` to open radare2 with a temporary memory buffer (malloc plugin).
|
||||
Use `r2 -w <file>` to open a file in write mode.
|
||||
Use `r2 -n <file>` to open a file without parsing headers.
|
||||
The `-p [prj]` flag creates a project for the analyzed file.
|
||||
Reopen a file in read-write mode using `oo+`.
|
||||
Reopen the current binary in debugger mode using `ood`.
|
||||
Quit radare2 using the `q` command.
|
||||
Run a script on startup using the `-i` command-line flag.
|
||||
Load a script or plugin within r2 using `. <script_path>`.
|
||||
Execute shell commands within r2 using `! <command>`.
|
||||
Pipe r2 command output to shell commands using `cmd | <shell_cmd>`.
|
||||
Interpret r2 command output as commands using `.cmd`. Example: `.is*`.
|
||||
Repeat the last command using `..` or Enter.
|
||||
Evaluate expressions using `?`. Example: `? 0x10 + 5`.
|
||||
`$$` represents the current virtual address (seek).
|
||||
Execute commands at specific offsets using `@`. Example: `px @ 0x1000`.
|
||||
Iterate commands over multiple offsets using `@@`. Example: `wx ff @@ 10 20 30`.
|
||||
Seek to an address or symbol using `s <address>`. Example: `s main`.
|
||||
Seek backward using `s- <num>`. Example: `s- 16`.
|
||||
Undo the last seek using `s-`.
|
||||
Redo the last seek using `s+`.
|
||||
Change the block size (default view size) using `b <size>`. Example: `b 256`.
|
||||
Print hex dump using `px [<num_bytes>]`.
|
||||
Print hex dump of words using `pxw [<num_words>]`.
|
||||
Print disassembled instructions using `pd [<num_instructions>]`.
|
||||
Print disassembled bytes using `pD [<num_bytes>]`.
|
||||
Print zero-terminated strings using `pz`.
|
||||
Print zero-terminated string at offset using `psz [<max_len>] @ <offset>`.
|
||||
Get file information using `i`.
|
||||
Get information about the binary format using `iI`.
|
||||
List entry points using `ie`.
|
||||
List imports using `ii`.
|
||||
List linked libraries using `il`.
|
||||
List sections using `iS`.
|
||||
List symbols using `is`.
|
||||
Get the symbol name at the current offset using `is.`.
|
||||
Extract strings from the data section using `iz`.
|
||||
Extract strings from the entire binary using `izz`.
|
||||
Search for strings using `/ <string>`. Example: `/ "password"`.
|
||||
Search for hex patterns using `/x <hexpairs>`. Example: `/x cafeba.e`.
|
||||
Search for assembly instructions using `/c <asm_string>`. Example: `/c jmp eax`.
|
||||
Assemble instructions and search for bytes using `/a <asm_string>`. Example: `/a mov eax, 1`.
|
||||
Search for ROP gadgets using `/R`.
|
||||
Define configuration variables using `e <var>=<value>`. Example: `e asm.arch=arm`.
|
||||
Check configuration variable values using `e <var>`. Example: `e asm.bits`.
|
||||
Access configuration values in expressions using `${var}`. Example: `? ${asm.bits}`.
|
||||
Set `asm.pseudo=true` for pseudo-syntax disassembly.
|
||||
Set `asm.describe=true` for inline opcode descriptions.
|
||||
Analyze all functions and code blocks using `aa`.
|
||||
Analyze functions using `af`.
|
||||
Analyze function at current offset using `af`.
|
||||
Analyze basic blocks within a function using `afb`.
|
||||
Analyze data references using `ad`. Example: `ad@rsp`.
|
||||
List analyzed functions using `afl`.
|
||||
Rename a function using `afn <new_name> [@<addr>]`.
|
||||
List cross-references (xrefs) *to* an address using `axt [<addr>]`.
|
||||
List cross-references (xrefs) *from* an address using `axf [<addr>]`.
|
||||
View instruction operand details using `ao`.
|
||||
List flags matching a pattern using `f~<pattern>`. Example: `f~sym`.
|
||||
List flags in a specific flagspace using `fs <space>; f`. Example: `fs imports; f`.
|
||||
Enable analysis of consecutive functions using `e anal.hasnext=true`.
|
||||
Start radare2 in debug mode using `r2 -d <file>`.
|
||||
Attach to a process using `r2 -d <pid>`.
|
||||
Step into the next instruction using `ds`.
|
||||
Step over the next instruction using `dso`.
|
||||
Continue execution using `dc`.
|
||||
Set a breakpoint using `db <address>`.
|
||||
List breakpoints using `db`.
|
||||
Remove a breakpoint using `db- <address>`.
|
||||
View register values using `dr`.
|
||||
View register values and dereferenced pointers using `drr`.
|
||||
Set `dbg.bep=entry` or `dbg.bep=main` to break at entry/main on start.
|
||||
Use `dp` to list processes after a fork.
|
||||
Use `dcf` to continue until a fork occurs.
|
||||
Enable tracing child processes on fork using `e dbg.follow.child=true`.
|
||||
Enter visual mode using `V`.
|
||||
Exit visual mode using `q`.
|
||||
Rotate visual modes (hex, disasm, etc.) using `p`/`P`.
|
||||
Toggle cursor mode using `c`.
|
||||
Move cursor using `hjkl`.
|
||||
Follow jump/call address using Enter.
|
||||
Step/Step Over in visual debug mode using `s`/`S`.
|
||||
Toggle breakpoints using `b`.
|
||||
Seek to program counter using `.`.
|
||||
Add/remove comments using `;`.
|
||||
Rename functions/variables in visual mode using `dr`.
|
||||
Define functions/data in visual mode using `d`.
|
||||
Switch between graph and panel views using Spacebar.
|
||||
Iaito is the official Qt-based GUI for radare2.
|
||||
`r2pm` is the radare2 package manager.
|
||||
Install plugins using `r2pm -ci <plugin_name>`. Example: `r2pm -ci r2ghidra`.
|
||||
The radare2 toolchain includes `rabin2`, `rasm2`, `rax2`, `rahash2`, `radiff2`, `rafind2`.
|
||||
`rabin2` extracts information from binary files (headers, symbols, sections).
|
||||
`rasm2` assembles and disassembles code for various architectures.
|
||||
`rax2` converts between bases and evaluates expressions.
|
||||
`rahash2` computes hashes and checksums.
|
||||
`radiff2` compares binary files.
|
||||
`rafind2` searches for patterns in files.
|
||||
`r2ghidra` is a decompiler plugin using Ghidra's decompiler engine.
|
||||
Use `pdg` for r2ghidra decompilation.
|
||||
r2ghidra quality may differ from native Ghidra due to analysis differences.
|
||||
`r2dec` is another decompiler plugin available via r2pm.
|
||||
Use `pdd` for r2dec decompilation.
|
||||
`pdc` is radare2's built-in pseudo-decompiler.
|
||||
`pdc` combines ESIL emulation and pseudo-assembly.
|
||||
`pdc` output can be verbose but works across many architectures.
|
||||
`decai` is an AI-powered plugin enhancing decompiler output readability.
|
||||
`decai` often uses `pdc` as a backend and feeds output to an AI model.
|
||||
`decai` can explain functions (`-x`), find vulnerabilities (`-V`), suggest names (`-n`).
|
||||
`r2ai` bridges radare2 with AI language models (local or remote).
|
||||
`r2ai` can run in direct mode (prompt -> AI) or auto mode (AI uses tools like `r2cmd`).
|
||||
`r2ai-model` is a dataset project for training LLMs about radare2.
|
||||
The r2ai-model dataset uses a TSV format (Question TAB r2-command-answer).
|
||||
`r2frida` integrates radare2 with the Frida dynamic instrumentation toolkit.
|
||||
Use `r2frida` commands starting with `:` (previously `\`).
|
||||
Trace function calls with `r2frida` using `:dt`.
|
||||
Format trace output and trace onEnter/onExit using `:dtf`.
|
||||
Use `:dtf` format specifiers like `z` (string), `c` (char), `x` (hex).
|
||||
`r2frida` can trace functions by address, import/export names.
|
||||
Use `:iE` in r2frida to enumerate exports of a loaded module.
|
||||
Trace specific call destinations found using analysis commands like `/at call`.
|
||||
`r2yara` integrates YARA rule matching into radare2.
|
||||
Install `r2yara` using `r2pm -ci r2yara`.
|
||||
`r2yara` commands start with `yr` or `yara`.
|
||||
Load YARA rules from a file using `yr <file>`.
|
||||
Scan the current file with loaded rules using `yrs`.
|
||||
List loaded YARA rules using `yrl`.
|
||||
Generate YARA rules interactively using `yrg` subcommands.
|
||||
Add strings to a generated rule using `yrgs`.
|
||||
Add hex patterns to a generated rule using `yrgx`.
|
||||
Add the currently generated rule using `yr+`.
|
||||
Flatpak releases of Iaito bundle plugins like r2dec, r2ghidra, r2frida, r2yara, decai.
|
||||
Optimize text for vector databases by using concise, factual statements.
|
||||
Rephrasing concepts helps improve embedding quality for semantic search.
|
||||
AI embeddings represent data (like text or code) in a high-dimensional space.
|
||||
Vector databases efficiently store and retrieve these embeddings.
|
||||
Embeddings help capture semantic relationships (e.g., King - Man + Woman ≈ Queen).
|
||||
Use `~<num>` to grep specific columns from command output. Example: `axt sym.main~[1]`.
|
||||
Use `~..` to view command output in a pager (like less).
|
||||
Use `~{}` to pretty-print JSON output. Example: `pdfj~{}`.
|
||||
Use `.radare2rc` file in the home directory for persistent configurations.
|
||||
Place `.r2` files in a binary's directory for project-specific scripts/settings.
|
||||
Count the functions found with aflc
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai-solid-black.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker></defs><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
|
||||
style="fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai-solid-white.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker></defs><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 325.38488,77.033582 c 0,0 -84.14563,42.868488 -84.89316,43.234548 l 12.82427,24.21005 h 72.06889 z m -142.20506,71.694888 49.85451,24.92726 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93531 -49.41068,24.47623 z m 70.6374,4.61097 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
d="m 467.89541,76.210715 c 0,0 -84.14563,42.868485 -84.89315,43.234545 l 12.82426,24.21005 h 72.06889 z m -142.20506,71.694895 49.85451,24.92725 12.85076,-24.2732 -13.29459,-25.13029 c -12.94186,6.3903 -34.27459,16.93532 -49.41068,24.47624 z m 70.6374,4.61096 -12.85482,24.27931 84.42248,42.80665 v -67.08596 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
d="M 183.41964,89.200886 V 213.41057 h 15.49313 v 7.01435 H 90.772581 v -7.01435 h 20.749649 v -67.21858 c 0,-11.98138 0.8776,-22.50487 -3.21182,-33.90204 -0.8776,-2.92232 -9.641954,-19.579945 -14.028698,-12.27479 1.17101,4.38544 1.755211,9.35247 1.755211,13.73921 0,19.57733 -13.445796,32.14422 -32.733634,32.14422 -20.165439,0 -34.192836,-14.02741 -34.192836,-34.19284 0,-21.04176 18.412827,-32.437632 37.700665,-32.437632 20.745742,0 37.403352,11.102474 47.055742,28.931102 h 0.57899 V 78.680007 c 28.05349,0.877612 56.4043,3.507829 84.46692,3.507829 v 7.01305 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.30402"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (https://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg3004"
|
||||
version="1.1"
|
||||
inkscape0:version="0.48.2 r9819"
|
||||
width="500"
|
||||
height="300"
|
||||
xml:space="preserve"
|
||||
sodipodi0:docname="r2.svg"
|
||||
sodipodi:docname="r2ai.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape0="https://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi0="https://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:ns0="https://creativecommons.org/ns#"
|
||||
xmlns:ns="https://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:ns1="https://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:zoom="1.207"
|
||||
inkscape:cx="170.25684"
|
||||
inkscape:cy="153.27258"
|
||||
inkscape:window-width="1714"
|
||||
inkscape:window-height="845"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3004" /><metadata
|
||||
id="metadata3010"><RDF><ns0:Work
|
||||
ns:about=""><ns1:format>image/svg+xml</ns1:format><ns1:type
|
||||
ns:resource="https://purl.org/dc/dcmitype/StillImage" /><ns1:title /></ns0:Work></RDF></metadata><defs
|
||||
id="defs3008"><marker
|
||||
style="overflow:visible"
|
||||
id="Stop"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="2"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23" /></marker><pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#pattern6"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern7"
|
||||
patternTransform="translate(-3.203125e-6)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3018"><path
|
||||
d="m 0,841.89 1190.55,0 L 1190.55,0 0,0 0,841.89 z"
|
||||
id="path3020"
|
||||
inkscape0:connector-curvature="0" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
patternTransform="translate(295.04028,-128.87021)"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
id="pattern6"><rect
|
||||
style="fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect6"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
ry="0.61871845" /></pattern><marker
|
||||
markerWidth="1.2374369"
|
||||
markerHeight="110.30865"
|
||||
refX="0.61871845"
|
||||
refY="55.154324"
|
||||
orient="auto"
|
||||
id="marker7"><rect
|
||||
style="fill:url(#pattern7);stroke:none"
|
||||
width="1.2374369"
|
||||
height="110.30865"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect7" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-3"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="1"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-6" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-3-3"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="1"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-6-5" /></marker><marker
|
||||
style="overflow:visible"
|
||||
id="Stop-6"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Stop"
|
||||
markerWidth="2"
|
||||
markerHeight="19"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="none"><path
|
||||
style="fill:none;stroke:context-stroke;stroke-width:1"
|
||||
d="M 0,4 V -4"
|
||||
id="path23-0" /></marker></defs><rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.755906"
|
||||
id="rect5"
|
||||
width="381.13055"
|
||||
height="146.37111"
|
||||
x="62.225395"
|
||||
y="141.42136"
|
||||
ry="0.6875" /><sodipodi0:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape0:pageopacity="0"
|
||||
inkscape0:pageshadow="2"
|
||||
inkscape0:window-width="791"
|
||||
inkscape0:window-height="818"
|
||||
id="namedview3006"
|
||||
showgrid="false"
|
||||
inkscape0:zoom="0.39912236"
|
||||
inkscape0:cx="715.69007"
|
||||
inkscape0:cy="676.77258"
|
||||
inkscape0:window-x="2687"
|
||||
inkscape0:window-y="256"
|
||||
inkscape0:window-maximized="0"
|
||||
inkscape0:current-layer="g3012" /><path
|
||||
id="path3496-8-6-5-6"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 409.32718,13.961811 c 0,0 -62.86723,31.580382 -63.41792,31.850056 l 9.44739,17.835078 h 53.97053 z m -105.6385,52.816247 36.72686,18.363427 9.46691,-17.881604 -9.79387,-18.512997 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.174863 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
id="path3496-8-6-5-6-6"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 303.71834,13.970537 c 0,0 -62.86723,31.580387 -63.41792,31.850061 l 9.44739,17.835077 h 53.97053 z m -105.6385,52.816251 36.72686,18.363428 9.46691,-17.881604 -9.79387,-18.512998 c -9.53402,4.707609 -25.24943,12.475918 -36.3999,18.031174 z m 52.03722,3.396805 -9.4699,17.8861 63.07118,31.534837 V 70.183593 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3)"
|
||||
d="m 303.83057,67.231304 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657"
|
||||
id="path7-5"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><path
|
||||
style="fill:#ececec;fill-opacity:1;stroke:#ffffff;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-3-3)"
|
||||
d="m 409.35891,66.9637 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 -3.29247,-0.01657 -3.29246,-0.01657 L 304,66.43346"
|
||||
id="path7-5-2"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /><g
|
||||
id="g5"><path
|
||||
id="path3496-8-6-5-6-6-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 306.19367,160.96204 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 V 217.1751 Z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop)"
|
||||
d="m 306.73753,215.43843 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
|
||||
id="path7-5-9"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><g
|
||||
id="g4"><path
|
||||
id="path3496-8-6-5-6-6-2-2"
|
||||
style="fill:#c8191c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
d="m 417.07189,160.35585 c 0,0 -61.98847,31.58039 -62.53916,31.85006 l 9.44739,17.83508 h 53.09177 z m -104.75974,52.81625 36.72686,18.36343 9.46691,-17.8816 -9.79387,-18.513 c -9.53402,4.70761 -25.24943,12.47592 -36.3999,18.03117 z m 52.03722,3.39681 -9.4699,17.8861 62.19242,31.53484 v -49.42094 z"
|
||||
sodipodi:nodetypes="ccccccccccccccc" /><path
|
||||
style="fill:#1a1a1a;fill-opacity:1;stroke:#000000;stroke-width:0.755906;stroke-dasharray:0, 8.31497;stroke-dashoffset:0;stroke-opacity:1;marker-mid:url(#Stop-6)"
|
||||
d="m 417.61575,214.83224 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0166 -3.29246,-0.0166 -3.29247,-0.0156"
|
||||
id="path7-5-9-6"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccc" /></g><path
|
||||
d="M 198.25651,22.933951 V 114.43684 H 209.67 v 5.16734 h -79.6648 v -5.16734 h 15.28587 V 64.918208 c 0,-8.826454 0.64651,-16.578901 -2.36609,-24.974984 -0.64651,-2.152817 -7.10304,-14.424164 -10.33467,-9.042602 0.86266,3.230669 1.29303,6.889785 1.29303,10.121413 0,14.422241 -9.90526,23.680027 -24.11424,23.680027 -14.855492,0 -25.189206,-10.333714 -25.189206,-25.189209 0,-15.501054 13.56438,-23.896176 27.773356,-23.896176 15.28299,0 27.55433,8.178976 34.66506,21.312988 h 0.42653 V 15.183424 c 20.66647,0.646518 41.55197,2.58415 62.22516,2.58415 v 5.166377 z"
|
||||
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
id="path3492-8"
|
||||
inkscape0:connector-curvature="0" /><path
|
||||
d="m 201.6106,169.92546 v 91.50289 h 11.41349 v 5.16734 h -79.6648 v -5.16734 h 15.28587 v -49.51864 c 0,-8.82645 0.64651,-16.5789 -2.36609,-24.97498 -0.64651,-2.15282 -7.10304,-14.42417 -10.33467,-9.0426 0.86266,3.23067 1.29303,6.88978 1.29303,10.12141 0,14.42224 -9.90526,23.68003 -24.11424,23.68003 -14.855492,0 -25.189206,-10.33372 -25.189206,-25.18921 0,-15.50106 13.564376,-23.89618 27.773356,-23.89618 15.28299,0 27.55433,8.17898 34.66506,21.31299 h 0.42653 v -21.74624 c 20.66647,0.64652 41.55197,2.58415 62.22516,2.58415 v 5.16638 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.96065"
|
||||
id="path3492-8-7"
|
||||
inkscape0:connector-curvature="0" /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,30 @@
|
||||
# Using kobold-ai from r2ai
|
||||
|
||||
KoboldCpp is a fork of llamacpp that exposes a server that r2ai can comuniate with to send queries. One of the main benefits is that it can handle large inputs on a variety of models and it's quite fast.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
r2pm -ci koboldcpp
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Start your browser and do your things
|
||||
|
||||
```
|
||||
koboldcpp --model /path/to/gguf
|
||||
```
|
||||
|
||||
Then connect r2ai to it using the koboldcpp model:
|
||||
|
||||
```
|
||||
r2ai '-m koboldcpp'
|
||||
```
|
||||
|
||||
which defaults to:
|
||||
|
||||
```
|
||||
r2ai '-m koboldcpp:http://localhost:5001'
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,66 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ALPHABET_SIZE 26
|
||||
|
||||
// Function to perform substitution (both encryption and decryption)
|
||||
void substitute(const char *input, const char *substitution, char *output, int reverse) {
|
||||
char mapping[ALPHABET_SIZE];
|
||||
|
||||
if (reverse) {
|
||||
// Create reverse substitution table for decryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[substitution[i] - 'A'] = 'A' + i;
|
||||
}
|
||||
} else {
|
||||
// Use the direct substitution table for encryption
|
||||
for (int i = 0; i < ALPHABET_SIZE; i++) {
|
||||
mapping[i] = substitution[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; input[i] != '\0'; i++) {
|
||||
char c = input[i];
|
||||
|
||||
// Handle uppercase letters
|
||||
if (isupper(c)) {
|
||||
output[i] = mapping[c - 'A'];
|
||||
}
|
||||
// Handle lowercase letters
|
||||
else if (islower(c)) {
|
||||
output[i] = tolower(mapping[c - 'a']);
|
||||
}
|
||||
// Leave non-alphabet characters unchanged
|
||||
else {
|
||||
output[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
output[strlen(input)] = '\0'; // Null-terminate the output string
|
||||
}
|
||||
|
||||
int main() {
|
||||
char input[256];
|
||||
char output[256];
|
||||
|
||||
// Configurable substitution key (A-Z)
|
||||
const char *substitution = "QWERTYUIOPASDFGHJKLZXCVBNM"; // Example substitution key
|
||||
|
||||
// Get input from the user
|
||||
printf("Enter text to encrypt: ");
|
||||
fgets(input, sizeof(input), stdin);
|
||||
|
||||
// Remove newline character from input if it exists
|
||||
input[strcspn(input, "\n")] = 0;
|
||||
|
||||
// Perform encryption (forward substitution)
|
||||
substitute(input, substitution, output, 0);
|
||||
printf("Encrypted text: %s\n", output);
|
||||
|
||||
// Perform decryption (reverse substitution)
|
||||
substitute(output, substitution, input, 1);
|
||||
printf("Decrypted text: %s\n", input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
all:
|
||||
deno --allow-all server.ts
|
||||
@@ -0,0 +1,70 @@
|
||||
// proxy_server.ts
|
||||
import { serve } from "https://deno.land/[email protected]/http/server.ts";
|
||||
|
||||
import VectorDB from "./vdb.ts"
|
||||
|
||||
const CONTEXT_SIZE = 5;
|
||||
const VECTOR_DIMENSION = 64;
|
||||
const TARGET_SERVER = "http://localhost:11434"; // 👈 change to your target
|
||||
const DATA_PATH = "../doc/data/quotes.txt";
|
||||
|
||||
function isIntercepted(url: string) {
|
||||
return url.startsWith("/api/chat");
|
||||
}
|
||||
|
||||
const db = new VectorDB(VECTOR_DIMENSION);
|
||||
const fileContent = await Deno.readTextFile(DATA_PATH);
|
||||
const lines = fileContent.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim().length > 0) {
|
||||
db.insert(line);
|
||||
// console.log("Line:", line);
|
||||
}
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
const url = new URL(req.url);
|
||||
const targetUrl = new URL(url.pathname + url.search, TARGET_SERVER);
|
||||
console.log(req);
|
||||
|
||||
let body = req.body;
|
||||
if (req.method === "POST" && req.url.endsWith("/api/chat")) {
|
||||
const obj = JSON.parse(await req.text());
|
||||
const newMessages = [];
|
||||
for (let msg of obj.messages) {
|
||||
let content = "<prompt>" + msg.content.trim() + "</prompt>\n";
|
||||
if (msg.role === "user") {
|
||||
const context = db.query(msg.content, CONTEXT_SIZE);
|
||||
for (const ctx of context) {
|
||||
console.log("<context>" + ctx);
|
||||
content += "<context>" + ctx + "</context>\n";
|
||||
}
|
||||
}
|
||||
console.log(content);
|
||||
msg.content = content;
|
||||
newMessages.push(msg);
|
||||
}
|
||||
obj.messages = newMessages;
|
||||
body = JSON.stringify(obj);
|
||||
}
|
||||
|
||||
const proxyReq = new Request(targetUrl.toString(), {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body: body,
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(proxyReq);
|
||||
const responseBody = response.body;
|
||||
const responseHeaders = new Headers(response.headers);
|
||||
return new Response(responseBody, {
|
||||
status: response.status,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Proxy error:", err);
|
||||
return new Response("Proxy error", { status: 502 });
|
||||
}
|
||||
}, { port: 8000 });
|
||||
@@ -242,6 +242,7 @@ class VectorDB {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Example usage
|
||||
function main() {
|
||||
// Create a vector database with 64-dimensional embeddings
|
||||
@@ -262,4 +263,5 @@ function main() {
|
||||
// Uncomment to run
|
||||
main();
|
||||
|
||||
*/
|
||||
export default VectorDB;
|
||||
@@ -1,9 +0,0 @@
|
||||
# r2ai-server
|
||||
|
||||
Start ollama, llamacpp, llamafile, r2ai, kobaldcpp and other language model webservers using a the same syntax for all of them to simplify its launching and setup.
|
||||
|
||||
* Use -l to list the implementations available
|
||||
|
||||
You can install more via r2pm
|
||||
|
||||
* Use -m to list or select the model. Those models can be absolute paths or model names when downloaded via r2ai, which uses the hugging face api
|
||||
@@ -1,7 +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)
|
||||
R2PLUGDIR=$(shell r2 -H R2_USER_PLUGINS)
|
||||
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
|
||||
@@ -35,7 +41,8 @@ OBJS+=openai.o
|
||||
OBJS+=markdown.o
|
||||
|
||||
all: $(OBJS) r2check
|
||||
$(CC) -fPIC -shared -o r2ai.$(EXT) $(OBJS) $(CFLAGS) $(LDFLAGS)
|
||||
$(CC) -fPIC -shared -o r2ai$(DOTLIB) $(OBJS) $(CFLAGS) $(LDFLAGS)
|
||||
$(CC) -pie -o r2ai$(DOTEXE) $(OBJS) $(CFLAGS) $(LDFLAGS) -lm main.c
|
||||
|
||||
try: all
|
||||
$(MAKE) user-install
|
||||
@@ -50,13 +57,19 @@ vdb.o: vdb_embed.inc.c
|
||||
indent:
|
||||
for a in *.c ; do python indent.py $$a ; done
|
||||
|
||||
install user-install:
|
||||
mkdir -p $(R2PLUGDIR)
|
||||
rm -f $(R2PLUGDIR)/r2ai.$(EXT)
|
||||
cp -f r2ai.$(EXT) $(R2PLUGDIR)
|
||||
user-install: user-uninstall
|
||||
mkdir -p $(R2USRPLUGDIR)
|
||||
cp -f r2ai$(DOTLIB) $(R2USRPLUGDIR)
|
||||
cp -f r2ai$(DOTEXE) $(R2BINDIR)
|
||||
|
||||
uninstall user-uninstall:
|
||||
cp -f $(R2PLUGDIR)/r2ai.$(EXT)
|
||||
user-uninstall:
|
||||
rm -f $(R2USRPLUGDIR)/r2ai$(DOTLIB)
|
||||
|
||||
install:
|
||||
cp -f r2ai$(DOTLIB) $(R2SYSPLUGDIR)
|
||||
|
||||
uninstall:
|
||||
rm -f $(R2SYSPLUGDIR)/r2ai$(DOTLIB)
|
||||
|
||||
r2check:
|
||||
@r2 -qcq --
|
||||
|
||||
@@ -34,16 +34,19 @@ make user-install
|
||||
[0x100003f58]> r2ai -h
|
||||
Usage: r2ai [-args] [...]
|
||||
| r2ai -d Decompile current function
|
||||
| r2ai -d [query] Ask a question on the current function
|
||||
| r2ai -dr Decompile current function (+ 1 level of recursivity)
|
||||
| r2ai -a [query] Resolve question using auto mode
|
||||
| r2ai -e Same as '-e r2ai.'
|
||||
| r2ai -h Show this help message
|
||||
| r2ai -i [file] [query] read file and ask the llm with the given query
|
||||
| r2ai -m show selected model, list suggested ones, choose one
|
||||
| r2ai -M show suggested models for each api
|
||||
| r2ai -n suggest a better name for the current function
|
||||
| r2ai -r enter the repl
|
||||
| r2ai -R ([text]) refresh and query embeddings (see r2ai.data)
|
||||
| r2ai -r enter the chat repl
|
||||
| r2ai -L show chat logs (See -Lj for json). Only for auto mode.
|
||||
| r2ai -L-[N] delete the last (or N last messages from the chat history)
|
||||
| r2ai -R reset the chat conversation context
|
||||
| r2ai -Rq ([text]) refresh and query embeddings (see r2ai.data)
|
||||
| r2ai -s function signature
|
||||
| r2ai -x explain current function
|
||||
| r2ai -v suggest better variables names and types
|
||||
@@ -52,20 +55,217 @@ Usage: r2ai [-args] [...]
|
||||
[0x100003f58]>
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
These can be set with `r2ai -e <keyname>=<value>`
|
||||
|
||||
| Setting name | Description |
|
||||
|------------------|------------------------------------------------------------------------------------------------|
|
||||
| r2ai.api | Name of the provider e.g `openai`. List possibilities with `r2ai -e r2ai.api=?` |
|
||||
| r2ai.model | Model name. List possibilities with `r2ai -e r2ai.model=?` |
|
||||
| r2ai.baseurl | Remote LLM base URL. Specify host if necessary e.g http://127.0.0.1:11434. |
|
||||
| r2ai.max_tokens | Maximum output tokens or maximum total tokens. Check the appropriate limits for your model |
|
||||
| r2ai.temperature | How creative the model should be. 0=not creative, 1=very creative |
|
||||
| r2ai.cmds | R2 command to issue and send output in context to model |
|
||||
| r2ai.lang | Tells LLM which programming language to use for decompilation result. Only works for `r2ai -d` |
|
||||
| r2ai.hlang | Tells LLM in which language to speak |
|
||||
| r2ai.prompt | User prompt to send to LLM with `r2ai -d` |
|
||||
| r2ai.auto.max_runs | Maximum loops when using auto mode `r2ai -a` |
|
||||
| r2ai.auto.hide_tool_output | Only for auto mode `r2ai -a`. Will not show the output of the tool which ran locally |
|
||||
| r2ai.auto.yolo | Set this to true if you don't want r2ai to ask you for approval before running commands sent by the LLM. This is **dangerous**. Recommendation: **leave this to false** unless you fully trust your LLM not to create havoc! |
|
||||
| r2ai.http.use_files | If you get CURL argument too long error, set this to true and arguments will be stored in a file.|
|
||||
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
See [https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
|
||||
- Get the [substitute example binary](https://github.com/radareorg/r2ai/tree/master/examples)
|
||||
- Initialize r2 and r2ai
|
||||
```
|
||||
$ r2 ./substitute
|
||||
[0x000010a0]> r2ai -e api=anthropic
|
||||
[0x000010a0]> r2ai -e model=claude-3-7-sonnet-20250219
|
||||
[0x000010a0]> aa
|
||||
```
|
||||
|
||||
- Decompile the main function: `r2ai -d`
|
||||
|
||||
```
|
||||
[0x000010a0]> s main
|
||||
[0x00001303]> r2ai -d
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char input[256];
|
||||
char encrypted[512];
|
||||
char *alphabet = "QWERTYUIOPASDFGHJKLZXCVBNM";
|
||||
|
||||
printf("Enter text to encrypt: ");
|
||||
fgets(input, 256, stdin);
|
||||
|
||||
input[strcspn(input, "\n")] = 0;
|
||||
|
||||
substitute(input, alphabet, encrypted, 0);
|
||||
printf("Encrypted text: %s\n", encrypted);
|
||||
|
||||
substitute(encrypted, alphabet, input, 1);
|
||||
printf("Decrypted text: %s\n", input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
- Ask a question on the current function: `r2ai -d query`
|
||||
|
||||
```
|
||||
[0x00001303]> r2ai -d what is input buffer size?
|
||||
Based on the disassembly provided, I can see that the input buffer size is 0x100 (256) bytes.
|
||||
|
||||
This is visible in the call to `fgets()` where we see:
|
||||
```
|
||||
esi = 0x100
|
||||
rdi = rax // pointing to var_110h which is the input buffer
|
||||
sym.imp.fgets() // char *fgets("", 0x00000100, 0)
|
||||
```
|
||||
|
||||
The fgets function is being called with a second argument of 0x100, which specifies the maximum number of characters to read (including the null terminator). This means the program allocates a 256-byte buffer for user input.
|
||||
```
|
||||
|
||||
Note that the context is **reset between each direct query**. For example, the context of the second command `r2ai -d what is input buffer size?` does not contain the request/response of the first `r2ai -d`.
|
||||
|
||||
- Find vulnerabilities: `r2ai -V`
|
||||
|
||||
```
|
||||
[0x00001303]> r2ai -V
|
||||
# Vulnerability Analysis
|
||||
|
||||
## Identified Vulnerabilities
|
||||
|
||||
1. **Buffer Overflow Risk**: The program uses `fgets()` with a 256-byte buffer to read user input, but doesn't validate if the substitute function's target buffer can handle this much data.
|
||||
|
||||
2. **Lack of Input Validation**: There appears to be no validation of the input character range, which could lead to unexpected behavior if special characters are used.
|
||||
|
||||
3. **Predictable Encryption**: The cipher uses a hardcoded key "QWERTYUIOPASDFGHJKLZXCVBNM", making it trivially breakable.
|
||||
|
||||
4. **Insufficient Memory Handling**: No verification that memory allocations succeed before use.
|
||||
|
||||
## Sample Exploit
|
||||
|
||||
```python
|
||||
# Buffer overflow exploit example
|
||||
from pwn import *
|
||||
|
||||
# Connect to the target (adjust as needed)
|
||||
p = process('./cipher_program')
|
||||
|
||||
# Create payload: 256 'A's + return address override
|
||||
payload = b"A" * 256 + p64(0x4141414141414141) # Example address
|
||||
|
||||
# Send the payload
|
||||
p.sendlineafter(b"Enter text to encrypt: ", payload)
|
||||
p.interactive()
|
||||
```
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
1. **Implement Buffer Size Checks**: Ensure that destination buffers are large enough for the operation and implement bounds checking.
|
||||
|
||||
2. **Input Validation**: Validate all user inputs for expected character ranges and length.
|
||||
|
||||
3. **Dynamic Key Generation**: Use cryptographically secure random number generators to create unique keys for each session.
|
||||
|
||||
4. **Memory Safety**: Use secure memory allocation practices with proper error handling.
|
||||
|
||||
5. **Modern Cryptography**: Replace the simple substitution cipher with standard cryptographic libraries (e.g., OpenSSL).
|
||||
|
||||
6. **Stack Protection**: Compile with stack canaries (`-fstack-protector-all`) and address space layout randomization (ASLR).
|
||||
|
||||
7. **Error Handling**: Implement proper error checking and graceful failure mechanisms.
|
||||
|
||||
8. **Secure Coding Practices**: Follow guidelines like OWASP for secure coding standards.
|
||||
```
|
||||
|
||||
|
||||
- Automatic mode: `r2ai -a query`. In the auto mode, r2ai offers use of tools.
|
||||
|
||||
```
|
||||
[0x00001303]> r2ai -a The main implements a substitution algorithm. Give me an input that would end up as RADARE2 when substituted
|
||||
[r2cmd]>
|
||||
[0x00001303]> afl~main
|
||||
afl~main
|
||||
|
||||
0x00001303 1 231 main
|
||||
|
||||
[Assistant]
|
||||
|
||||
I'll analyze the substitution algorithm in the main function to determine what input would result in "RADARE2" after substitution. Let me examine the main function code.
|
||||
[r2cmd]>
|
||||
[0x00001303]> pdf @ main
|
||||
...
|
||||
```
|
||||
|
||||
- See the chat logs (only in auto mode): `r2ai -L`:
|
||||
|
||||
```
|
||||
[r2ai] Chat Logs (12 messages)
|
||||
Note: System prompt is applied automatically but not stored in history
|
||||
|
||||
[user]: The main implements a substitution algorithm. Give me an input that would end up as RADARE2 when substituted
|
||||
|
||||
[assistant]: I'll analyze the substitution algorithm in the main function to determine what input would result in "RADARE2" after substitution. Let me examine the main function code.
|
||||
[tool call]: r2cmd
|
||||
{"command":"pdf @ main"}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
* add "undo" command to drop the last message
|
||||
* dump / restore conversational states (see -L command)
|
||||
* Implement `~`, `|` and `>` and other r2shell features
|
||||
* keep context between direct commands unless explicitly reset
|
||||
* modify context if tool command is user edited
|
||||
|
||||
|
||||
## Messages API
|
||||
|
||||
The r2ai project includes a new API for managing messages and tool calls in conversations with AI models. This API is implemented in `messages.c` and `messages.h`.
|
||||
|
||||
### Key Functions
|
||||
|
||||
- `r2ai_messages_new()`: Create a new messages array
|
||||
- `r2ai_messages_free(msgs)`: Free a messages array and all associated data
|
||||
- `r2ai_messages_add(msgs, role, content, tool_call_id)`: Add a new message
|
||||
- `r2ai_messages_add_tool_call(msgs, name, arguments, id)`: Add a tool call to the last message
|
||||
- `r2ai_messages_last(msgs)`: Get the last message in the array
|
||||
- `r2ai_messages_parse_json(msgs, json)`: Parse a JSON response into messages
|
||||
- `r2ai_messages_clear(msgs)`: Clear all messages
|
||||
|
||||
### Example Usage
|
||||
|
||||
```c
|
||||
[0x100003f58]> r2ai -e api=claude
|
||||
[0x100003f58]> r2ai -d
|
||||
int main(int argc, char **argv, char **envp) {
|
||||
char buffer[32];
|
||||
int result = 0;
|
||||
if (argv[1] != NULL) {
|
||||
strcpy_chk(buffer, argv[1], 32);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
[0x100003f58]>
|
||||
// Create a new messages array
|
||||
R2AI_Messages *msgs = r2ai_messages_new();
|
||||
|
||||
// Add a user message
|
||||
r2ai_messages_add(msgs, "user", "What is the disassembly of function main?", NULL);
|
||||
|
||||
// Add an assistant message with a tool call
|
||||
R2AI_Message *msg = r2ai_messages_add(msgs, "assistant", NULL, NULL);
|
||||
r2ai_messages_add_tool_call(msgs, "r2cmd", "{\"command\":\"pdf@main\"}", "tool-123");
|
||||
|
||||
// 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
|
||||
@@ -68,7 +68,7 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
|
||||
PJ *pj = pj_new ();
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "model", modelname (model));
|
||||
pj_kn (pj, "max_tokens", 4096);
|
||||
pj_kn (pj, "max_tokens", args.max_tokens);
|
||||
if (args.thinking_tokens >= 1024) {
|
||||
pj_ko (pj, "thinking");
|
||||
pj_ks (pj, "type", "enabled");
|
||||
@@ -104,7 +104,7 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
|
||||
|
||||
// Make the API call
|
||||
int code = 0;
|
||||
char *res = r2ai_http_post (anthropic_url, headers, data, &code, NULL);
|
||||
char *res = r2ai_http_post (core, anthropic_url, headers, data, &code, NULL);
|
||||
free (data);
|
||||
free (auth_header);
|
||||
|
||||
@@ -316,6 +316,8 @@ R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args) {
|
||||
}
|
||||
|
||||
R_IPI char *r2ai_anthropic_stream(RCore *core, R2AIArgs args) {
|
||||
(void)core;
|
||||
(void)args;
|
||||
// Not implemented yet
|
||||
return NULL;
|
||||
}
|
||||
@@ -24,14 +24,14 @@ static R2AIStats stats = { 0 };
|
||||
static char *format_time_duration(time_t seconds) {
|
||||
if (seconds < 60) {
|
||||
return r_str_newf ("%llds", (long long)seconds);
|
||||
} else if (seconds < 3600) {
|
||||
return r_str_newf ("%lldm%llds", (long long)(seconds / 60), (long long)(seconds % 60));
|
||||
} else {
|
||||
return r_str_newf ("%lldh%lldm%llds",
|
||||
(long long)(seconds / 3600),
|
||||
(long long)((seconds % 3600) / 60),
|
||||
(long long)(seconds % 60));
|
||||
}
|
||||
if (seconds < 3600) {
|
||||
return r_str_newf ("%lldm%llds", (long long)(seconds / 60), (long long)(seconds % 60));
|
||||
}
|
||||
return r_str_newf ("%lldh%lldm%llds",
|
||||
(long long)(seconds / 3600),
|
||||
(long long)((seconds % 3600) / 60),
|
||||
(long long)(seconds % 60));
|
||||
}
|
||||
|
||||
// Initialize timing and cost tracking for a run
|
||||
@@ -54,6 +54,8 @@ static void r2ai_stats_init_run(int n_run) {
|
||||
|
||||
// Print a simple run indicator at the start
|
||||
static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run, int max_runs) {
|
||||
(void)n_run;
|
||||
(void)max_runs;
|
||||
time_t run_time = time (NULL) - stats.start_time;
|
||||
time_t total_time = time (NULL) - stats.total_start_time;
|
||||
if (usage) {
|
||||
@@ -64,16 +66,19 @@ static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run,
|
||||
stats.total_prompt_tokens += usage->prompt_tokens;
|
||||
stats.total_completion_tokens += usage->completion_tokens;
|
||||
}
|
||||
// TODO: calculate cost
|
||||
stats.run_cost = 0.0 * run_time;
|
||||
stats.total_cost += stats.run_cost;
|
||||
|
||||
if (r_config_get_b (core->config, "r2ai.chat.show_cost") == true) {
|
||||
// TODO: calculate cost
|
||||
stats.run_cost = 0.0 * run_time;
|
||||
stats.total_cost += stats.run_cost;
|
||||
}
|
||||
|
||||
// Format times for display
|
||||
char *run_time_str = format_time_duration (run_time);
|
||||
char *total_time_str = format_time_duration (total_time);
|
||||
|
||||
// Print detailed stats
|
||||
r_cons_printf ("\x1b[1;34m%s | total: %d in: %d out: %d | run: %d in: %d out: %d | %s / %s\x1b[0m\n",
|
||||
R2_PRINTF ("\x1b[1;34m%s | total: %d in: %d out: %d | run: %d in: %d out: %d | %s / %s\x1b[0m\n",
|
||||
r_config_get (core->config, "r2ai.model"),
|
||||
stats.total_tokens,
|
||||
stats.total_prompt_tokens,
|
||||
@@ -83,8 +88,8 @@ static void r2ai_print_run_end(RCore *core, const R2AI_Usage *usage, int n_run,
|
||||
stats.run_completion_tokens,
|
||||
run_time_str,
|
||||
total_time_str);
|
||||
r_cons_newline ();
|
||||
r_cons_flush ();
|
||||
R2_NEWLINE ();
|
||||
R2_FLUSH ();
|
||||
|
||||
free (run_time_str);
|
||||
free (total_time_str);
|
||||
@@ -115,22 +120,23 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
|
||||
char *error = NULL;
|
||||
bool interrupted = false;
|
||||
const int max_runs = r_config_get_i (core->config, "r2ai.auto.max_runs");
|
||||
|
||||
if (n_run > max_runs) {
|
||||
r_cons_printf ("\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("\x1b[1;31m[r2ai] Max runs reached\x1b[0m\n");
|
||||
R2_FLUSH ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!system_prompt) {
|
||||
if (R_STR_ISNOTEMPTY (r_config_get (core->config, "r2ai.auto.init_commands"))) {
|
||||
const char *init_commands = r_config_get (core->config, "r2ai.auto.init_commands");
|
||||
if (init_commands) {
|
||||
char *cmd_output = execute_tool (core, "r2cmd", r_str_newf ("{\"command\":\"%s\"}", init_commands));
|
||||
if (cmd_output) {
|
||||
system_prompt = r_str_newf ("%s\n\nHere is some information about the binary to get you started:\n>%s\n%s", Gprompt_auto, init_commands, cmd_output);
|
||||
free (cmd_output);
|
||||
}
|
||||
const char *init_commands = r_config_get (core->config, "r2ai.auto.init_commands");
|
||||
if (R_STR_ISNOTEMPTY (init_commands)) {
|
||||
char *edited_command = NULL;
|
||||
char *cmd_output = execute_tool (core, "r2cmd", r_str_newf ("{\"command\":\"%s\"}", init_commands), &edited_command);
|
||||
if (cmd_output) {
|
||||
system_prompt = r_str_newf ("%s\n\nHere is some information about the binary to get you started:\n>%s\n%s", Gprompt_auto, edited_command, cmd_output);
|
||||
free (cmd_output);
|
||||
}
|
||||
free (edited_command);
|
||||
} else {
|
||||
system_prompt = Gprompt_auto;
|
||||
}
|
||||
@@ -164,12 +170,18 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
|
||||
}
|
||||
|
||||
// Process the response - we need to add it to our messages array
|
||||
if (message->content) {
|
||||
char *assistant_msg = r_str_newf ("\x1b[31m[Assistant]\x1b[0m\n\n%s\n", message->content);
|
||||
r_cons_printf ("%s", assistant_msg);
|
||||
r_cons_newline ();
|
||||
r_cons_flush ();
|
||||
free (assistant_msg);
|
||||
if (message->content || message->reasoning_content) {
|
||||
R2_PRINTF ("\x1b[31m[Assistant]\x1b[0m\n\n");
|
||||
if (message->reasoning_content) {
|
||||
R2_PRINTF ("\x1b[90m<thinking>\n%s\n</thinking>\x1b[0m\n", message->reasoning_content);
|
||||
R2_NEWLINE ();
|
||||
R2_FLUSH ();
|
||||
}
|
||||
if (message->content) {
|
||||
R2_PRINTF ("%s", message->content);
|
||||
R2_NEWLINE ();
|
||||
R2_FLUSH ();
|
||||
}
|
||||
}
|
||||
|
||||
// Add the response to our messages array
|
||||
@@ -178,7 +190,7 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
|
||||
|
||||
// Check for tool calls and process them
|
||||
if (message->tool_calls && message->n_tool_calls > 0) {
|
||||
R_LOG_DEBUG ("Found %d tool calls", message->n_tool_calls);
|
||||
R_LOG_DEBUG ("Found %d tool call (s)", message->n_tool_calls);
|
||||
|
||||
// Process each tool call
|
||||
for (int i = 0; i < message->n_tool_calls; i++) {
|
||||
@@ -189,7 +201,6 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
|
||||
}
|
||||
R_LOG_DEBUG ("Tool call: %s", tool_call->name);
|
||||
// Don't log the full arguments which might get truncated
|
||||
R_LOG_DEBUG ("Processing tool arguments...");
|
||||
char *tool_name = strdup (tool_call->name);
|
||||
char *tool_args = strdup (tool_call->arguments);
|
||||
if (!tool_name || !tool_args) {
|
||||
@@ -203,14 +214,17 @@ R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *sy
|
||||
if (interrupted) {
|
||||
cmd_output = strdup ("<user interrupted>");
|
||||
} else {
|
||||
cmd_output = execute_tool (core, tool_name, tool_args);
|
||||
char *edited_command = NULL;
|
||||
cmd_output = execute_tool (core, tool_name, tool_args, &edited_command);
|
||||
// TODO: need to edit the R2AI_Messages* and modify the command of the last tool_use
|
||||
free (edited_command);
|
||||
}
|
||||
|
||||
free (tool_name);
|
||||
free (tool_args);
|
||||
if (strcmp (cmd_output, "R2AI_SIGINT") == 0) {
|
||||
r_cons_printf ("\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("\n\n\x1b[1;31m[r2ai] Processing interrupted after tool execution\x1b[0m\n\n");
|
||||
R2_FLUSH ();
|
||||
free (cmd_output);
|
||||
cmd_output = strdup ("<user interrupted>");
|
||||
interrupted = true;
|
||||
@@ -267,27 +281,27 @@ R_IPI void cmd_r2ai_a(RCore *core, const char *user_query) {
|
||||
}
|
||||
|
||||
// Helper function to display content with length indication for long content
|
||||
static void print_content_with_length(const char *content, const char *empty_msg, bool always_show_length) {
|
||||
static void print_content_with_length(RCore *core, const char *content, const char *empty_msg, bool always_show_length) {
|
||||
if (!content || *content == '\0') {
|
||||
r_cons_printf ("%s\n", empty_msg ? empty_msg : "<no content>");
|
||||
R2_PRINTF ("%s\n", empty_msg ? empty_msg : "<no content>");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t content_len = strlen (content);
|
||||
const int max_display = 200;
|
||||
const size_t max_display = 200;
|
||||
|
||||
if (content_len > max_display) {
|
||||
// Truncate long content and show length
|
||||
char *truncated = r_str_ndup (content, max_display);
|
||||
r_cons_printf ("%s... \x1b[1;37m(length: %zu chars)\x1b[0m\n",
|
||||
R2_PRINTF ("%s... \x1b[1;37m(length: %zu chars)\x1b[0m\n",
|
||||
truncated, content_len);
|
||||
free (truncated);
|
||||
} else if (always_show_length) {
|
||||
// Always show length for certain types (like tool responses)
|
||||
r_cons_printf ("%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
|
||||
R2_PRINTF ("%s \x1b[1;37m(length: %zu chars)\x1b[0m\n",
|
||||
content, content_len);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", content);
|
||||
R2_PRINTF ("%s\n", content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +310,7 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
|
||||
// Get conversation
|
||||
R2AI_Messages *messages = r2ai_conversation_get ();
|
||||
if (!messages || messages->n_messages == 0) {
|
||||
r_cons_printf ("No conversation history available\n");
|
||||
R2_PRINTF ("No conversation history available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -347,16 +361,16 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
|
||||
pj_end (pj);
|
||||
|
||||
char *json_str = pj_drain (pj);
|
||||
r_cons_printf ("%s\n", json_str);
|
||||
R2_PRINTF ("%s\n", json_str);
|
||||
free (json_str);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
r_cons_printf ("\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
|
||||
messages->n_messages);
|
||||
R2_PRINTF ("\x1b[1;34m[r2ai] Chat Logs (%d messages)\x1b[0m\n",
|
||||
core->cons, messages->n_messages);
|
||||
|
||||
r_cons_printf ("\x1b[1;33mNote: System prompt is applied automatically but not stored in history\x1b[0m\n\n");
|
||||
R2_PRINTF ("\x1b[1;33mNote: System prompt is applied automatically but not stored in history\x1b[0m\n\n");
|
||||
|
||||
// Display each message in the conversation
|
||||
for (int i = 0; i < messages->n_messages; i++) {
|
||||
@@ -365,55 +379,35 @@ R_IPI void cmd_r2ai_logs(RCore *core) {
|
||||
|
||||
// Format based on role
|
||||
if (!strcmp (role, "user")) {
|
||||
r_cons_printf ("\x1b[1;32m[user]:\x1b[0m ");
|
||||
print_content_with_length (msg->content, "<no content>", false);
|
||||
R2_PRINTF ("\x1b[1;32m[user]:\x1b[0m ");
|
||||
print_content_with_length (core, msg->content, "<no content>", false);
|
||||
} else if (!strcmp (role, "assistant")) {
|
||||
r_cons_printf ("\x1b[1;36m[assistant]:\x1b[0m ");
|
||||
print_content_with_length (msg->content, "<no content>", false);
|
||||
R2_PRINTF ("\x1b[1;36m[assistant]:\x1b[0m ");
|
||||
print_content_with_length (core, msg->content, "<no content>", false);
|
||||
// Show tool calls if present
|
||||
if (msg->tool_calls && msg->n_tool_calls > 0) {
|
||||
for (int j = 0; j < msg->n_tool_calls; j++) {
|
||||
const R2AI_ToolCall *tc = &msg->tool_calls[j];
|
||||
r_cons_printf (" \x1b[1;35m[tool call]:\x1b[0m %s\n",
|
||||
R2_PRINTF (" \x1b[1;35m[tool call]:\x1b[0m %s\n",
|
||||
tc->name ? tc->name : "<unnamed>");
|
||||
|
||||
if (tc->arguments) {
|
||||
r_cons_printf (" %s\n", tc->arguments);
|
||||
R2_PRINTF (" %s\n", tc->arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcmp (role, "tool")) {
|
||||
r_cons_printf ("\x1b[1;35m[tool]:\x1b[0m ");
|
||||
print_content_with_length (msg->content, "<no result>", true);
|
||||
R2_PRINTF ("\x1b[1;35m[tool]:\x1b[0m ");
|
||||
print_content_with_length (core, msg->content, "<no result>", true);
|
||||
|
||||
// Don't show the tool call ID as requested
|
||||
} else {
|
||||
// Other roles (system, etc.)
|
||||
r_cons_printf ("\x1b[1;37m[%s]:\x1b[0m ", role);
|
||||
print_content_with_length (msg->content, "<no content>", false);
|
||||
R2_PRINTF ("\x1b[1;37m[%s]:\x1b[0m ", role);
|
||||
print_content_with_length (core, msg->content, "<no content>", false);
|
||||
}
|
||||
|
||||
r_cons_newline ();
|
||||
r_cons_flush ();
|
||||
R2_NEWLINE ();
|
||||
R2_FLUSH ();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a conversation with optional initial user message
|
||||
R_API R2AI_Messages *create_conversation(const char *system_prompt, const char *user_message) {
|
||||
// Create a temporary message container (not using static storage)
|
||||
R2AI_Messages *msgs = r2ai_msgs_new ();
|
||||
if (!msgs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add user message if provided (no system message - that's added during processing)
|
||||
if (user_message) {
|
||||
R2AI_Message user_msg = {
|
||||
.role = "user",
|
||||
.content = user_message
|
||||
};
|
||||
r2ai_msgs_add (msgs, &user_msg);
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Copyright r2ai - 2023-2025 - pancake */
|
||||
|
||||
#include <r_core.h>
|
||||
#include "r2ai.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
eprintf ("Usage: r2ai <prompt>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *prompt = argv[1];
|
||||
|
||||
RCore *core = r_core_new ();
|
||||
char *err = NULL;
|
||||
|
||||
R2AIArgs args = {
|
||||
.input = prompt,
|
||||
.error = &err,
|
||||
.dorag = false,
|
||||
};
|
||||
|
||||
char *res = r2ai (core, args);
|
||||
if (res) {
|
||||
R_LOG_INFO (res);
|
||||
free (res);
|
||||
}
|
||||
free (err);
|
||||
r_core_free (core);
|
||||
return 0;
|
||||
}
|
||||
@@ -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/).'
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "r2ai.h"
|
||||
|
||||
// AITODO: remove globals
|
||||
// Static global message store for session persistence
|
||||
static R2AI_Messages *conversation = NULL;
|
||||
|
||||
@@ -13,9 +14,10 @@ R_API void r2ai_message_free(R2AI_Message *msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
free ((void *)msg->role);
|
||||
free ((void *)msg->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) {
|
||||
@@ -30,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)
|
||||
@@ -55,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) {
|
||||
@@ -161,6 +157,7 @@ R_API bool r2ai_msgs_add(R2AI_Messages *msgs, const R2AI_Message *msg) {
|
||||
R2AI_Message *dest = &msgs->messages[msgs->n_messages++];
|
||||
dest->role = msg->role ? strdup (msg->role) : NULL;
|
||||
dest->content = msg->content ? strdup (msg->content) : NULL;
|
||||
dest->reasoning_content = msg->reasoning_content ? strdup (msg->reasoning_content) : NULL;
|
||||
|
||||
if (msg->content_blocks) {
|
||||
R2AI_ContentBlocks *cb = R_NEW0 (R2AI_ContentBlocks);
|
||||
@@ -406,6 +403,10 @@ R_API char *r2ai_msgs_to_json(const R2AI_Messages *msgs) {
|
||||
pj_ks (pj, "content", msg->content);
|
||||
}
|
||||
|
||||
if (msg->reasoning_content) {
|
||||
pj_ks (pj, "reasoning_content", msg->reasoning_content);
|
||||
}
|
||||
|
||||
// Add tool_call_id if present
|
||||
if (msg->tool_call_id) {
|
||||
pj_ks (pj, "tool_call_id", msg->tool_call_id);
|
||||
@@ -558,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);
|
||||
@@ -641,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) {
|
||||
@@ -688,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) {
|
||||
@@ -743,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);
|
||||
@@ -762,9 +757,25 @@ 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 pj? pj_drain (pj): NULL;
|
||||
}
|
||||
|
||||
// Create a conversation with optional initial user message
|
||||
R_API R2AI_Messages *create_conversation(const char *user_message) {
|
||||
// Create a temporary message container (not using static storage)
|
||||
R2AI_Messages *msgs = r2ai_msgs_new ();
|
||||
if (!msgs) {
|
||||
return NULL;
|
||||
}
|
||||
char *result = pj_drain (pj);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add user message if provided (no system message - that's added during processing)
|
||||
if (R_STR_ISNOTEMPTY (user_message)) {
|
||||
R2AI_Message user_msg = {
|
||||
.role = "user",
|
||||
.content = user_message
|
||||
};
|
||||
r2ai_msgs_add (msgs, &user_msg);
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ typedef struct {
|
||||
// Hash table to store model compatibility info
|
||||
static HtPP *model_compat_db = NULL;
|
||||
|
||||
#if 0
|
||||
// Function to check if a model has a specific error flag
|
||||
static bool model_has_error(const char *provider, const char *model, ModelErrorFlags flag) {
|
||||
if (!model_compat_db) {
|
||||
@@ -30,9 +31,8 @@ static bool model_has_error(const char *provider, const char *model, ModelErrorF
|
||||
}
|
||||
|
||||
char *key = r_str_newf ("%s:%s", provider, model ? model : "default");
|
||||
ModelCompat *compat = NULL;
|
||||
bool found_flag = false;
|
||||
compat = ht_pp_find (model_compat_db, key, &found_flag);
|
||||
ModelCompat *compat = ht_pp_find (model_compat_db, key, &found_flag);
|
||||
free (key);
|
||||
|
||||
if (found_flag && compat) {
|
||||
@@ -40,6 +40,7 @@ static bool model_has_error(const char *provider, const char *model, ModelErrorF
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Function to add an error flag to a model
|
||||
static void model_add_error(const char *provider, const char *model, ModelErrorFlags flag) {
|
||||
@@ -66,6 +67,8 @@ static void model_add_error(const char *provider, const char *model, ModelErrorF
|
||||
|
||||
// Free a ModelCompat item (for hash table)
|
||||
static bool model_compat_free_cb(void *user, const void *k, const void *v) {
|
||||
(void)user;
|
||||
(void)k;
|
||||
ModelCompat *compat = (ModelCompat *)v;
|
||||
if (compat) {
|
||||
free (compat->model_id);
|
||||
@@ -84,34 +87,12 @@ R_IPI void r2ai_openai_fini(void) {
|
||||
}
|
||||
|
||||
R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
const char *base_url = r_config_get (core->config, "r2ai.base_url");
|
||||
|
||||
// Initialize compatibility database if needed
|
||||
if (!model_compat_db) {
|
||||
model_compat_db = ht_pp_new0 ();
|
||||
}
|
||||
|
||||
if (R_STR_ISEMPTY (base_url)) {
|
||||
if (strcmp (args.provider, "openai") == 0) {
|
||||
base_url = "https://api.openai.com/v1";
|
||||
} else if (strcmp (args.provider, "gemini") == 0) {
|
||||
base_url = "https://generativelanguage.googleapis.com/v1beta/openai";
|
||||
} else if (strcmp (args.provider, "ollama") == 0) {
|
||||
base_url = "http://localhost:11434/v1";
|
||||
} else if (strcmp (args.provider, "xai") == 0) {
|
||||
base_url = "https://api.x.ai/v1";
|
||||
} else if (strcmp (args.provider, "anthropic") == 0) { // also "claude" ?
|
||||
base_url = "https://api.anthropic.com/v1";
|
||||
} else if (strcmp (args.provider, "openapi") == 0) {
|
||||
base_url = "http://127.0.0.1:11434";
|
||||
} else if (strcmp (args.provider, "openrouter") == 0) {
|
||||
base_url = "https://openrouter.ai/api/v1";
|
||||
} else if (strcmp (args.provider, "groq") == 0) { // isnt the same as x.ai?
|
||||
base_url = "https://api.groq.com/openai/v1";
|
||||
} else if (strcmp (args.provider, "mistral") == 0) {
|
||||
base_url = "https://api.mistral.ai/v1";
|
||||
}
|
||||
}
|
||||
const char *base_url = r2ai_get_provider_url (core, args.provider);
|
||||
// TODO: default model name should depend on api
|
||||
const char *model_name = args.model ? args.model : "gpt-4o-mini";
|
||||
char **error = args.error;
|
||||
@@ -169,10 +150,24 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
*error = NULL;
|
||||
}
|
||||
|
||||
char *auth_header = r_str_newf ("Authorization: Bearer %s", args.api_key);
|
||||
R_LOG_DEBUG ("Auth header: %s", auth_header);
|
||||
const char *headers[] = { "Content-Type: application/json", auth_header, NULL };
|
||||
const char *openai_url = r_str_newf ("%s/chat/completions", base_url);
|
||||
const char **headers = NULL;
|
||||
char *auth_header = NULL;
|
||||
if (R_STR_ISNOTEMPTY (args.api_key)) {
|
||||
auth_header = r_str_newf ("Authorization: Bearer %s", args.api_key);
|
||||
R_LOG_DEBUG ("Auth header: %s", auth_header);
|
||||
static const char *static_headers[] = { NULL, NULL, NULL };
|
||||
headers = static_headers;
|
||||
headers[0] = "Content-Type: application/json";
|
||||
headers[1] = auth_header;
|
||||
}
|
||||
|
||||
const char *openai_url;
|
||||
if (strcmp (args.provider, "ollama") == 0) {
|
||||
openai_url = r_str_newf ("%s/chat", base_url);
|
||||
} else {
|
||||
openai_url = r_str_newf ("%s/chat/completions", base_url);
|
||||
}
|
||||
R_LOG_DEBUG ("OpenAI URL: %s", openai_url);
|
||||
|
||||
// Create a messages JSON object, either from input messages or from content
|
||||
char *messages_json = NULL;
|
||||
@@ -207,10 +202,14 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
pj_ks (pj, "model", model_name);
|
||||
pj_kb (pj, "stream", false);
|
||||
|
||||
#if 0
|
||||
// gpt-5-mini-chat is the only gpt-5 model that supports temperature
|
||||
// gpt-5 gpt-5-mini and gpt-5-nano just throw an error
|
||||
// Only add temperature if this provider/model doesn't have the temperature error flag
|
||||
if (!model_has_error (args.provider, model_name, MODEL_ERROR_TEMPERATURE)) {
|
||||
pj_kd (pj, "temperature", args.temperature ? args.temperature : 0.01);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcmp (args.provider, "mistral") == 0) {
|
||||
pj_kn (pj, "max_tokens", args.max_tokens ? args.max_tokens : 5128);
|
||||
@@ -273,7 +272,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
// Make the API call
|
||||
char *res = NULL;
|
||||
int code = 0;
|
||||
res = r2ai_http_post (openai_url, headers, complete_json, &code, NULL);
|
||||
res = r2ai_http_post (core, openai_url, headers, complete_json, &code, NULL);
|
||||
free (complete_json);
|
||||
|
||||
if (code != 200) {
|
||||
@@ -282,7 +281,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
R_LOG_ERROR ("OpenAI API error response: %s", res);
|
||||
// Check for specific error types in the response
|
||||
ModelErrorFlags error_flag = MODEL_ERROR_NONE;
|
||||
const char *model_name = args.model ? args.model : "gpt-4o-mini";
|
||||
const char *model_name = args.model ? args.model : "gpt-5-mini";
|
||||
|
||||
// Check for temperature errors
|
||||
if (strstr (res, "temperature")) {
|
||||
@@ -351,6 +350,7 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
if (message_json) {
|
||||
const RJson *role = r_json_get (message_json, "role");
|
||||
const RJson *content = r_json_get (message_json, "content");
|
||||
const RJson *reasoning_content = r_json_get (message_json, "reasoning_content");
|
||||
|
||||
// Set the basic message properties
|
||||
message->role = (role && role->type == R_JSON_STRING) ? strdup (role->str_value) : strdup ("assistant");
|
||||
@@ -359,6 +359,10 @@ R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args) {
|
||||
message->content = strdup (content->str_value);
|
||||
}
|
||||
|
||||
if (reasoning_content && reasoning_content->type == R_JSON_STRING) {
|
||||
message->reasoning_content = strdup (reasoning_content->str_value);
|
||||
}
|
||||
|
||||
// Handle tool calls if present
|
||||
const RJson *tool_calls = r_json_get (message_json, "tool_calls");
|
||||
if (tool_calls && tool_calls->type == R_JSON_ARRAY && tool_calls->children.count > 0) {
|
||||
@@ -366,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;
|
||||
@@ -419,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;
|
||||
}
|
||||
@@ -48,8 +48,10 @@ static void refresh_embeddings(RCore *core) {
|
||||
}
|
||||
|
||||
static RCoreHelpMessage help_msg_r2ai = {
|
||||
"Usage:", "r2ai", " [-args] [...]",
|
||||
"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.'",
|
||||
@@ -58,7 +60,7 @@ static RCoreHelpMessage help_msg_r2ai = {
|
||||
"r2ai", " -m", "show selected model, list suggested ones, choose one",
|
||||
"r2ai", " -n", "suggest a better name for the current function",
|
||||
"r2ai", " -r", "enter the chat repl",
|
||||
"r2ai", " -L", "show chat logs (See -Lj for json)",
|
||||
"r2ai", " -L", "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)",
|
||||
@@ -70,51 +72,33 @@ static RCoreHelpMessage help_msg_r2ai = {
|
||||
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;
|
||||
@@ -123,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);
|
||||
|
||||
@@ -131,43 +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;
|
||||
|
||||
const char *e_api_key = r_config_get (core->config, "r2ai.api_key");
|
||||
if (R_STR_ISNOTEMPTY (e_api_key)) {
|
||||
api_key = strdup (e_api_key);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
free (absolute_apikey);
|
||||
if (strcmp (provider, "ollama") && strcmp (provider, "openapi")) {
|
||||
api_key = r2ai_get_api_key (core, provider);
|
||||
if (api_key) {
|
||||
args.api_key = api_key;
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
R_LOG_ERROR ("No API key found for %s provider. Please set one with: r2ai -e %s.api_key=YOUR_KEY",
|
||||
provider, provider);
|
||||
char *Provider = strdup (provider);
|
||||
r_str_case (Provider, true);
|
||||
R_LOG_ERROR ("No API key found for %s provider. Please set one with: r2ai "
|
||||
"%s_API_KEY=YOUR_KEY",
|
||||
provider, Provider);
|
||||
free (Provider);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -200,10 +168,7 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
|
||||
r_vdb_result_free (rs);
|
||||
}
|
||||
char *m = r_strbuf_drain (sb);
|
||||
R2AI_Message msg = {
|
||||
.role = "user",
|
||||
.content = m
|
||||
};
|
||||
R2AI_Message msg = { .role = "user", .content = m };
|
||||
context_pullback = args.messages->n_messages;
|
||||
r2ai_msgs_add (args.messages, &msg);
|
||||
free (m);
|
||||
@@ -212,10 +177,7 @@ R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args) {
|
||||
|
||||
// Add the rest of the messages one by one
|
||||
if (!args.messages && args.input) {
|
||||
R2AI_Message msg = {
|
||||
.role = "user",
|
||||
.content = args.input
|
||||
};
|
||||
R2AI_Message msg = { .role = "user", .content = args.input };
|
||||
args.messages = r2ai_msgs_new ();
|
||||
r2ai_msgs_add (args.messages, &msg);
|
||||
}
|
||||
@@ -242,19 +204,27 @@ 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] [provider] [prompt]");
|
||||
*args.error = r_str_newf ("Usage: r2ai [-h] [prompt]");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set system_prompt from config if it's not already set
|
||||
if (!args.system_prompt) {
|
||||
if (R_STR_ISEMPTY (args.system_prompt)) {
|
||||
args.system_prompt = r_config_get (core->config, "r2ai.system");
|
||||
}
|
||||
|
||||
R2AI_Messages *msgs = r2ai_msgs_new ();
|
||||
|
||||
if (args.input) {
|
||||
R2AI_Message msg = { .role = "user", .content = args.input };
|
||||
r2ai_msgs_add (msgs, &msg);
|
||||
}
|
||||
|
||||
args.messages = msgs;
|
||||
|
||||
// Call the r2ai_llmcall function to get the message
|
||||
R2AI_ChatResponse *res = r2ai_llmcall (core, args);
|
||||
if (!res) {
|
||||
@@ -283,11 +253,22 @@ R_IPI char *r2ai(RCore *core, R2AIArgs args) {
|
||||
}
|
||||
|
||||
static void cmd_r2ai_d(RCore *core, const char *input, const bool recursive) {
|
||||
const bool r2ai_stream = r_config_get_b (core->config, "r2ai.stream");
|
||||
r_config_set_b (core->config, "r2ai.stream", false);
|
||||
const char *prompt = r_config_get (core->config, "r2ai.prompt");
|
||||
const char *lang = r_config_get (core->config, "r2ai.lang");
|
||||
char *full_prompt;
|
||||
if (!R_STR_ISEMPTY (input)) {
|
||||
R_LOG_DEBUG ("User question: %s", input);
|
||||
full_prompt = strdup (input);
|
||||
} else {
|
||||
if (!R_STR_ISEMPTY (lang)) {
|
||||
full_prompt = r_str_newf (
|
||||
"%s. Translate the code into %s programming language.", prompt, lang);
|
||||
} else {
|
||||
full_prompt = strdup (prompt);
|
||||
}
|
||||
}
|
||||
char *cmds = strdup (r_config_get (core->config, "r2ai.cmds"));
|
||||
RStrBuf *sb = r_strbuf_new (prompt);
|
||||
RStrBuf *sb = r_strbuf_new (full_prompt);
|
||||
RList *cmdslist = r_str_split_list (cmds, ",", -1);
|
||||
RListIter *iter;
|
||||
const char *cmd;
|
||||
@@ -322,31 +303,31 @@ static void cmd_r2ai_d(RCore *core, const char *input, const bool recursive) {
|
||||
r_list_free (refslist);
|
||||
char *s = r_strbuf_drain (sb);
|
||||
char *error = NULL;
|
||||
R2AIArgs d_args = {
|
||||
.input = s,
|
||||
.error = &error,
|
||||
.dorag = true
|
||||
};
|
||||
R2AIArgs d_args = { .input = s, .error = &error, .dorag = true };
|
||||
char *res = r2ai (core, d_args);
|
||||
free (s);
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTF ("%s\n", res);
|
||||
}
|
||||
free (res);
|
||||
r_list_free (cmdslist);
|
||||
r_config_set_b (core->config, "r2ai.stream", r2ai_stream);
|
||||
}
|
||||
|
||||
static void cmd_r2ai_x(RCore *core) {
|
||||
const char *hlang = r_config_get (core->config, "r2ai.hlang");
|
||||
char *explain_prompt = r_str_newf ("Analyze function calls, comments and strings, ignore registers and memory accesess. Considering the references and involved loops make explain the purpose of this function in one or two short sentences. Output must be only the translation of the explanation in %s", hlang);
|
||||
char *explain_prompt = r_str_newf (
|
||||
"Analyze function calls, comments and strings, ignore registers and "
|
||||
"memory accesess. Considering the references and involved loops make "
|
||||
"explain the purpose of this function in one or two short sentences. "
|
||||
"Output must be only the translation of the explanation in %s",
|
||||
hlang);
|
||||
char *s = r_core_cmd_str (core, "r2ai -d");
|
||||
|
||||
// Create conversation
|
||||
R2AI_Messages *msgs = create_conversation (NULL, s);
|
||||
R2AI_Messages *msgs = create_conversation (s);
|
||||
if (!msgs) {
|
||||
R_LOG_ERROR ("Failed to create conversation");
|
||||
free (s);
|
||||
@@ -367,16 +348,21 @@ static void cmd_r2ai_x(RCore *core) {
|
||||
static void cmd_r2ai_repl(RCore *core) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
while (true) {
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
r_line_set_prompt (core->cons->line, ">>> ");
|
||||
const char *ptr = r_line_readline (core->cons);
|
||||
#else
|
||||
r_line_set_prompt (">>> ");
|
||||
const char *ptr = r_line_readline ();
|
||||
#endif
|
||||
if (R_STR_ISEMPTY (ptr)) {
|
||||
break;
|
||||
}
|
||||
if (*ptr == '/') {
|
||||
if (ptr[1] == '?' || r_str_startswith (ptr, "/help")) {
|
||||
r_cons_println ("/help show this help");
|
||||
r_cons_println ("/reset reset conversation");
|
||||
r_cons_println ("/quit same as ^D, leave the repl");
|
||||
R2_PRINTLN ("/help show this help");
|
||||
R2_PRINTLN ("/reset reset conversation");
|
||||
R2_PRINTLN ("/quit same as ^D, leave the repl");
|
||||
} else if (r_str_startswith (ptr, "/reset")) {
|
||||
r_strbuf_free (sb);
|
||||
sb = r_strbuf_new ("");
|
||||
@@ -388,14 +374,15 @@ static void cmd_r2ai_repl(RCore *core) {
|
||||
r_strbuf_appendf (sb, "User: %s\n", ptr);
|
||||
const char *a = r_strbuf_tostring (sb);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = a, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = a, .error = &error, .dorag = true });
|
||||
if (error) {
|
||||
R_LOG_ERROR ("%s", error);
|
||||
free (error);
|
||||
} else if (res) {
|
||||
r_strbuf_appendf (sb, "Assistant: %s\n", res);
|
||||
r_cons_println (res);
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("%s\n", res);
|
||||
R2_FLUSH ();
|
||||
}
|
||||
free (res);
|
||||
}
|
||||
@@ -427,8 +414,8 @@ static void cmd_r2ai_R(RCore *core, const char *q) {
|
||||
KDNode *n = r->node;
|
||||
float dist_sq = r->dist_sq;
|
||||
float cos_sim = 1.0f - (dist_sq * 0.5f); // for normalized vectors
|
||||
printf ("%2d) dist_sq=%.4f cos_sim=%.4f text=\"%s\"\n",
|
||||
i + 1, dist_sq, cos_sim, (n->text ? n->text : "(null)"));
|
||||
printf ("%2d) dist_sq=%.4f cos_sim=%.4f text=\"%s\"\n", i + 1, dist_sq,
|
||||
cos_sim, (n->text ? n->text : "(null)"));
|
||||
}
|
||||
r_vdb_result_free (rs);
|
||||
} else {
|
||||
@@ -439,15 +426,21 @@ static void cmd_r2ai_R(RCore *core, const char *q) {
|
||||
|
||||
static void cmd_r2ai_n(RCore *core) {
|
||||
char *s = r_core_cmd_str (core, "r2ai -d");
|
||||
char *q = r_str_newf ("output only the radare2 commands in plain text without markdown. Give me a better name for this function. the output must be: 'afn NEWNAME'. do not include the function code, only the afn line. consider: \n```c\n%s\n```", s);
|
||||
char *q =
|
||||
r_str_newf ("output only the radare2 commands in plain text without "
|
||||
"markdown. Give me a better name for this function. the "
|
||||
"output must be: 'afn NEWNAME'. do not include the function "
|
||||
"code, only the afn line. consider: \n```c\n%s\n```",
|
||||
s);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
free (s);
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTF ("%s\n", res);
|
||||
}
|
||||
free (res);
|
||||
free (q);
|
||||
@@ -467,13 +460,14 @@ static void cmd_r2ai_i(RCore *core, const char *arg) {
|
||||
}
|
||||
char *q = r_str_newf ("%s\n```\n%s\n```\n", query, s);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
free (s);
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTF ("%s\n", res);
|
||||
}
|
||||
free (fname);
|
||||
free (res);
|
||||
@@ -490,9 +484,16 @@ static void cmd_r2ai_s(RCore *core) {
|
||||
free (afv);
|
||||
return;
|
||||
}
|
||||
char *q = r_str_newf ("analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the resturn. Do NOT print the function body, ONLY output the function signature, ignore '@' in argument types because it must be used in a C header:\n```\n%s\n``` source code:\n```\n%s\n```\n", afv, s);
|
||||
char *q = r_str_newf (
|
||||
"analyze the uses of the arguments and return value to infer the "
|
||||
"signature, identify which is the correct type for the resturn. Do NOT "
|
||||
"print the function body, ONLY output the function signature, ignore '@' "
|
||||
"in argument types because it must be used in a C header:\n```\n%s\n``` "
|
||||
"source code:\n```\n%s\n```\n",
|
||||
afv, s);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
@@ -507,7 +508,7 @@ static void cmd_r2ai_s(RCore *core) {
|
||||
*end = 0;
|
||||
}
|
||||
r_str_trim (nl);
|
||||
r_cons_printf ("'afs %s\n", nl);
|
||||
R2_PRINTF ("'afs %s\n", nl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -520,14 +521,19 @@ static void cmd_r2ai_s(RCore *core) {
|
||||
static void cmd_r2ai_v(RCore *core) {
|
||||
char *s = r_core_cmd_str (core, "r2ai -d");
|
||||
char *afv = r_core_cmd_str (core, "afv");
|
||||
char *q = r_str_newf ("Output only the radare2 command without markdown, guess a better name and type for each local variable and function argument taking using. output an r2 script using afvn and afvt commands:\n```\n%s```", afv);
|
||||
char *q = r_str_newf (
|
||||
"Output only the radare2 command without markdown, guess a better name "
|
||||
"and type for each local variable and function argument taking using. "
|
||||
"output an r2 script using afvn and afvt commands:\n```\n%s```",
|
||||
afv);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTLN (res);
|
||||
}
|
||||
free (afv);
|
||||
free (res);
|
||||
@@ -537,14 +543,18 @@ static void cmd_r2ai_v(RCore *core) {
|
||||
|
||||
static void cmd_r2ai_V(RCore *core, bool recursive) {
|
||||
char *s = r_core_cmd_str (core, recursive ? "r2ai -d" : "r2ai -dr");
|
||||
char *q = r_str_newf ("find vulnerabilities, dont show the code, only show the response, provide a sample exploit and suggest good practices:\n```\n%s```", s);
|
||||
char *q = r_str_newf (
|
||||
"find vulnerabilities, dont show the code, only show the response, "
|
||||
"provide a sample exploit and suggest good practices:\n```\n%s```",
|
||||
s);
|
||||
char *error = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = q, .error = &error, .dorag = true });
|
||||
if (error) {
|
||||
R_LOG_ERROR (error);
|
||||
free (error);
|
||||
} else {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTF ("%s\n", res);
|
||||
}
|
||||
free (res);
|
||||
free (q);
|
||||
@@ -553,13 +563,13 @@ static void cmd_r2ai_V(RCore *core, bool recursive) {
|
||||
|
||||
static void cmd_r2ai_m(RCore *core, const char *input) {
|
||||
if (R_STR_ISEMPTY (input)) {
|
||||
r_cons_printf ("%s\n", r_config_get (core->config, "r2ai.model"));
|
||||
R2_PRINTF ("%s\n", r_config_get (core->config, "r2ai.model"));
|
||||
return;
|
||||
}
|
||||
r_config_lock (core->config, false);
|
||||
r_config_set (core->config, "r2ai.model", input);
|
||||
r_config_lock (core->config, true);
|
||||
r_cons_printf ("Model set to %s\n", input);
|
||||
R2_PRINTF ("Model set to %s\n", input);
|
||||
}
|
||||
|
||||
static void load_embeddings(RCore *core, RVdb *db) {
|
||||
@@ -612,11 +622,11 @@ static void cmd_r2ai(RCore *core, const char *input) {
|
||||
const int N = atoi (arg);
|
||||
R2AI_Messages *messages = r2ai_conversation_get ();
|
||||
if (!messages || messages->n_messages == 0) {
|
||||
r_cons_printf ("No conversation history available\n");
|
||||
R2_PRINTF ("No conversation history available\n");
|
||||
} else {
|
||||
r2ai_delete_last_messages (messages, N);
|
||||
r_cons_printf ("Deleted %d message%s from chat history\n",
|
||||
N > 0 ? N : 1, (N > 0 && N != 1) ? "s" : "");
|
||||
R2_PRINTF ("Deleted %d message%s from chat history\n", N > 0 ? N : 1,
|
||||
(N > 0 && N != 1) ? "s" : "");
|
||||
}
|
||||
} else if (r_str_startswith (input, "-L")) {
|
||||
cmd_r2ai_logs (core);
|
||||
@@ -642,7 +652,7 @@ static void cmd_r2ai(RCore *core, const char *input) {
|
||||
for (i = 0; i < rs->size; i++) {
|
||||
RVdbResult *r = &rs->results[i];
|
||||
KDNode *n = r->node;
|
||||
r_cons_printf ("- (%.4f) %s\n", r->dist_sq, n->text);
|
||||
R2_PRINTF ("- (%.4f) %s\n", r->dist_sq, n->text);
|
||||
}
|
||||
r_vdb_result_free (rs);
|
||||
}
|
||||
@@ -659,21 +669,13 @@ static void cmd_r2ai(RCore *core, const char *input) {
|
||||
} else if (r_str_startswith (input, "-r")) {
|
||||
cmd_r2ai_repl (core);
|
||||
} else if (r_str_startswith (input, "-R")) {
|
||||
#if 0
|
||||
if (strlen (input) <= 2 || isspace (input[2])) {
|
||||
R2AI_Messages *messages = r2ai_conversation_get ();
|
||||
if (!messages || messages->n_messages == 0) {
|
||||
r_cons_printf ("No conversation history to reset\n");
|
||||
} else {
|
||||
r2ai_msgs_clear (messages);
|
||||
r_cons_printf ("Chat conversation context has been reset\n");
|
||||
}
|
||||
R2AI_Messages *messages = r2ai_conversation_get ();
|
||||
if (!messages || messages->n_messages == 0) {
|
||||
R2_PRINTF ("No conversation history to reset\n");
|
||||
} else {
|
||||
cmd_r2ai_R (core, r_str_trim_head_ro (input + 2));
|
||||
r2ai_msgs_clear (messages);
|
||||
R2_PRINTF ("Chat conversation context has been reset\n");
|
||||
}
|
||||
#else
|
||||
cmd_r2ai_R (core, r_str_trim_head_ro (input + 2));
|
||||
#endif
|
||||
} else if (r_str_startswith (input, "-Rq")) {
|
||||
cmd_r2ai_R (core, r_str_trim_head_ro (input + 3));
|
||||
} else if (r_str_startswith (input, "-m")) {
|
||||
@@ -682,30 +684,165 @@ static void cmd_r2ai(RCore *core, const char *input) {
|
||||
r_core_cmd_help (core, help_msg_r2ai);
|
||||
} else {
|
||||
char *err = NULL;
|
||||
char *res = r2ai (core, (R2AIArgs){ .input = input, .error = &err, .dorag = true });
|
||||
char *res =
|
||||
r2ai (core, (R2AIArgs){ .input = input, .error = &err, .dorag = true });
|
||||
if (err) {
|
||||
R_LOG_ERROR ("%s", err);
|
||||
R_FREE (err);
|
||||
}
|
||||
if (res) {
|
||||
r_cons_printf ("%s\n", res);
|
||||
R2_PRINTF ("%s\n", res);
|
||||
free (res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider) {
|
||||
if (strcmp (provider, "openai") == 0) {
|
||||
const char *host = r_config_get (core->config, "r2ai.baseurl");
|
||||
if (R_STR_ISNOTEMPTY (host)) {
|
||||
if (r_str_startswith (host, "http")) {
|
||||
return r_str_newf ("%s/v1", host);
|
||||
}
|
||||
return r_str_newf ("http://%s/v1", host);
|
||||
}
|
||||
return "https://api.openai.com/v1";
|
||||
} else if (strcmp (provider, "gemini") == 0) {
|
||||
return "https://generativelanguage.googleapis.com/v1beta/openai";
|
||||
} else if (strcmp (provider, "ollama") == 0) {
|
||||
const char *host = r_config_get (core->config, "r2ai.baseurl");
|
||||
if (R_STR_ISNOTEMPTY (host)) {
|
||||
if (r_str_startswith (host, "http")) {
|
||||
return r_str_newf ("%s/api", host);
|
||||
}
|
||||
return r_str_newf ("http://%s/api", host);
|
||||
}
|
||||
return "http://localhost:11434/api";
|
||||
} else if (strcmp (provider, "xai") == 0) {
|
||||
return "https://api.x.ai/v1";
|
||||
} else if (strcmp (provider, "anthropic") == 0) {
|
||||
return "https://api.anthropic.com/v1";
|
||||
} else if (strcmp (provider, "openapi") == 0) {
|
||||
return "http://127.0.0.1:11434";
|
||||
} else if (strcmp (provider, "openrouter") == 0) {
|
||||
return "https://openrouter.ai/api/v1";
|
||||
} else if (strcmp (provider, "groq") == 0) {
|
||||
return "https://api.groq.com/openai/v1";
|
||||
} else if (strcmp (provider, "mistral") == 0) {
|
||||
return "https://api.mistral.ai/v1";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static RList *fetch_available_models(RCore *core, const char *provider) {
|
||||
const char *purl = r2ai_get_provider_url (core, provider);
|
||||
if (!purl) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create models endpoint URL
|
||||
char *models_url = NULL;
|
||||
if (strcmp (provider, "ollama") != 0) {
|
||||
models_url = r_str_newf ("%s/models", purl);
|
||||
} else {
|
||||
models_url = r_str_newf ("%s/tags", purl);
|
||||
}
|
||||
if (!models_url) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get API key for authentication (except for ollama and openapi)
|
||||
char *api_key = NULL;
|
||||
if (strcmp (provider, "ollama") && strcmp (provider, "openapi")) {
|
||||
// Consolidated helper to fetch the API key from env or file
|
||||
api_key = r2ai_get_api_key (core, provider);
|
||||
}
|
||||
|
||||
int code = 0;
|
||||
char *response = NULL;
|
||||
if (api_key) {
|
||||
const char *headers[4] = { "Content-Type: application/json", NULL, NULL, NULL };
|
||||
char *auth_header = NULL;
|
||||
char *version_header = NULL;
|
||||
|
||||
if (strcmp (provider, "anthropic") == 0) {
|
||||
// Anthropic uses different header format
|
||||
auth_header = r_str_newf ("x-api-key: %s", api_key);
|
||||
version_header = strdup ("anthropic-version: 2023-06-01");
|
||||
headers[1] = auth_header;
|
||||
headers[2] = version_header;
|
||||
} else {
|
||||
// Standard OpenAI-compatible format
|
||||
auth_header = r_str_newf ("Authorization: Bearer %s", api_key);
|
||||
headers[1] = auth_header;
|
||||
}
|
||||
|
||||
// Make HTTP GET request
|
||||
R_LOG_DEBUG ("GET %s Headers: %s", models_url, headers);
|
||||
response = r2ai_http_get (models_url, headers, &code, NULL);
|
||||
free (auth_header);
|
||||
free (version_header);
|
||||
free (api_key);
|
||||
} else {
|
||||
// We have no headers
|
||||
R_LOG_DEBUG ("GET %s", models_url);
|
||||
response = r2ai_http_get (models_url, NULL, &code, NULL);
|
||||
}
|
||||
|
||||
free (models_url);
|
||||
|
||||
if (!response || code != 200) {
|
||||
R_LOG_DEBUG ("Failed to fetch models from %s (code: %d)", provider, code);
|
||||
free (response);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Parse JSON response
|
||||
RList *models = r_list_newf (free);
|
||||
if (!models) {
|
||||
free (response);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RJson *json = r_json_parse (response);
|
||||
if (json) {
|
||||
const RJson *data;
|
||||
if (strcmp (provider, "ollama") != 0) {
|
||||
data = r_json_get (json, "data");
|
||||
} else {
|
||||
data = r_json_get (json, "models");
|
||||
}
|
||||
|
||||
if (data && data->type == R_JSON_ARRAY) {
|
||||
const RJson *model_item = data->children.first;
|
||||
while (model_item) {
|
||||
const RJson *id;
|
||||
if (strcmp (provider, "ollama") != 0) {
|
||||
id = r_json_get (model_item, "id");
|
||||
} else {
|
||||
id = r_json_get (model_item, "model");
|
||||
}
|
||||
if (id && id->type == R_JSON_STRING && R_STR_ISNOTEMPTY (id->str_value)) {
|
||||
R_LOG_DEBUG ("Model: %s", id->str_value);
|
||||
r_list_append (models, strdup (id->str_value));
|
||||
}
|
||||
model_item = model_item->next;
|
||||
}
|
||||
}
|
||||
r_json_free (json);
|
||||
}
|
||||
|
||||
free (response);
|
||||
return models;
|
||||
}
|
||||
|
||||
static bool cb_r2ai_api(void *user, void *data) {
|
||||
RCore *core = (RCore *)user;
|
||||
RConfigNode *node = (RConfigNode *)data;
|
||||
if (*node->value == '?') {
|
||||
r_cons_println ("ollama");
|
||||
r_cons_println ("openai");
|
||||
r_cons_println ("openapi");
|
||||
r_cons_println ("anthropic");
|
||||
r_cons_println ("gemini");
|
||||
r_cons_println ("openrouter");
|
||||
r_cons_println ("mistral");
|
||||
r_cons_println ("groq");
|
||||
r_cons_println ("xai");
|
||||
const char apis[] = "ollama\nopenai\nopenapi\nanthropic\ngemini\nopenrouter\nmistral\ngroq\nxai";
|
||||
R2_PRINTLN (apis);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -716,68 +853,133 @@ static bool cb_r2ai_model(void *user, void *data) {
|
||||
RCore *core = (RCore *)user;
|
||||
const char *api = r_config_get (core->config, "r2ai.api");
|
||||
if (*node->value == '?') {
|
||||
if (!strcmp (api, "anthropic")) {
|
||||
r_cons_println ("-m claude-3-7-sonnet-20250219");
|
||||
r_cons_println ("-m claude-3-5-sonnet-20241022");
|
||||
r_cons_println ("-m claude-3-haiku-20240307");
|
||||
} else if (!strcmp (api, "gemini")) {
|
||||
r_cons_println ("-m gemini-1.5-flash");
|
||||
r_cons_println ("-m gemini-1.0-pro");
|
||||
} else if (!strcmp (api, "openai")) {
|
||||
r_cons_println ("-m gpt-4");
|
||||
r_cons_println ("-m gpt-4o-mini");
|
||||
r_cons_println ("-m gpt-3.5-turbo");
|
||||
} else if (!strcmp (api, "ollama")) {
|
||||
r_sys_cmd ("ollama ls");
|
||||
// Try to fetch models dynamically first
|
||||
RList *models = fetch_available_models (core, api);
|
||||
if (models && !r_list_empty (models)) {
|
||||
RListIter *iter;
|
||||
char *model;
|
||||
r_list_foreach (models, iter, model) {
|
||||
R2_PRINTLN (model);
|
||||
}
|
||||
r_list_free (models);
|
||||
} else {
|
||||
// Fallback to static lists if dynamic fetching fails
|
||||
if (!strcmp (api, "gemini")) {
|
||||
R2_PRINTLN ("gemini-1.5-flash");
|
||||
R2_PRINTLN ("gemini-1.0-pro");
|
||||
} else if (!strcmp (api, "ollama")) {
|
||||
char *s = r_sys_cmd_str ("ollama ls", NULL, NULL);
|
||||
if (s) {
|
||||
RList *items = r_str_split_list (s, "\n", 0);
|
||||
RListIter *iter;
|
||||
char *item;
|
||||
r_list_foreach (items, iter, item) {
|
||||
if (R_STR_ISEMPTY (item) || r_str_startswith (item, "NAME")) {
|
||||
continue;
|
||||
}
|
||||
char *s = strchr (item, ' ');
|
||||
if (s) {
|
||||
*s = 0;
|
||||
}
|
||||
R2_PRINTLN (item);
|
||||
}
|
||||
r_list_free (items);
|
||||
free (s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
static bool r2ai_init(RCorePluginSession *cps) {
|
||||
RCore *core = cps->core;
|
||||
#else
|
||||
static int r2ai_init(void *user, const char *input) {
|
||||
RCmd *cmd = (RCmd *)user;
|
||||
RCore *core = cmd->data;
|
||||
#endif
|
||||
|
||||
// Initialize conversation container
|
||||
r2ai_conversation_init ();
|
||||
|
||||
r_config_lock (core->config, false);
|
||||
r_config_set_cb (core->config, "r2ai.api", "openai", &cb_r2ai_api);
|
||||
r_config_set_cb (core->config, "r2ai.model", "gpt-4o-mini", &cb_r2ai_model);
|
||||
r_config_set (core->config, "r2ai.base_url", "");
|
||||
r_config_set (core->config, "r2ai.api_key", ""); // TODO: deprecate for privacy reasons
|
||||
r_config_set_i (core->config, "r2ai.max_tokens", 5128);
|
||||
r_config_desc (core->config, "r2ai.api", "LLM provider to use (openai, gemini, anthropic, ollama, ...)");
|
||||
r_config_set_cb (core->config, "r2ai.model", "gpt-5-mini", &cb_r2ai_model);
|
||||
r_config_desc (core->config, "r2ai.model", "Model identifier for the selected provider (e.g. gpt-5-mini)");
|
||||
r_config_set (core->config, "r2ai.baseurl", "");
|
||||
r_config_desc (core->config, "r2ai.baseurl", "Base URL for provider API (overrides default endpoints)");
|
||||
r_config_set_i (core->config, "r2ai.max_tokens", 4096); // max output tokens, or max total tokens
|
||||
r_config_desc (core->config, "r2ai.max_tokens", "Maximum tokens for LLM responses (output/total depending on provider)");
|
||||
r_config_set_i (core->config, "r2ai.thinking_tokens", 0);
|
||||
r_config_desc (core->config, "r2ai.thinking_tokens", "Number of tokens reserved for internal thinking/context messages");
|
||||
r_config_set (core->config, "r2ai.temperature", "0.01");
|
||||
r_config_desc (core->config, "r2ai.temperature", "Sampling temperature for LLM output (0 = deterministic)");
|
||||
r_config_set (core->config, "r2ai.cmds", "pdc");
|
||||
r_config_desc (core->config, "r2ai.cmds", "Default command sequence used by automation (e.g. 'pdc')");
|
||||
r_config_set (core->config, "r2ai.lang", "C");
|
||||
r_config_desc (core->config, "r2ai.lang", "Programming language hint used in prompts (e.g. C, C++, Rust)");
|
||||
r_config_set_b (core->config, "r2ai.data", false);
|
||||
r_config_desc (core->config, "r2ai.data", "Enable local data/embeddings for query context retrieval");
|
||||
r_config_set_b (core->config, "r2ai.data.reason", false);
|
||||
r_config_desc (core->config, "r2ai.data.reason", "Include reasoning/explanations from local data when building context");
|
||||
r_config_set (core->config, "r2ai.data.path", "/tmp/embeds");
|
||||
r_config_desc (core->config, "r2ai.data.path", "Path to local embeddings/text files used for context (one .txt per document)");
|
||||
r_config_set_i (core->config, "r2ai.data.nth", 10);
|
||||
r_config_desc (core->config, "r2ai.data.nth", "Number of top-matching documents to include from local data for context");
|
||||
r_config_set (core->config, "r2ai.hlang", "english");
|
||||
r_config_set (core->config, "r2ai.system", "Your name is r2clippy");
|
||||
r_config_set (core->config, "r2ai.prompt", "Rewrite this function and respond ONLY with code, replace goto/labels with if/else/for, use NO explanations, NO markdown, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'");
|
||||
r_config_desc (core->config, "r2ai.hlang", "Human language for prompts/messages (e.g. english)");
|
||||
r_config_set (
|
||||
core->config, "r2ai.system",
|
||||
"You are a reverse engineer. The user is reversing a binary, using "
|
||||
"radare2. The user will ask questions about the binary and you will "
|
||||
"respond with the answer to the best of your ability.");
|
||||
r_config_set (
|
||||
core->config, "r2ai.prompt",
|
||||
"Rewrite this function and respond ONLY with code, replace goto/labels "
|
||||
"with if/else/for, use NO explanations, NO markdown, Simplify as much as "
|
||||
"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_set_i (core->config, "r2ai.auto.max_runs", 100);
|
||||
r_config_desc (core->config, "r2ai.stream", "Enable streaming responses from the LLM (true/false)");
|
||||
r_config_set_i (core->config, "r2ai.auto.max_runs", 50);
|
||||
r_config_desc (core->config, "r2ai.auto.max_runs", "Maximum number of automated steps/runs in auto mode");
|
||||
r_config_set_b (core->config, "r2ai.auto.hide_tool_output", false);
|
||||
r_config_desc (core->config, "r2ai.auto.hide_tool_output", "Hide tool output when running automated actions");
|
||||
r_config_set (core->config, "r2ai.auto.init_commands", "aaa;iI;afl");
|
||||
r_config_set_b (core->config, "r2ai.auto.ask_to_execute", true);
|
||||
r_config_desc (core->config, "r2ai.auto.init_commands", "Initial commands executed when auto mode starts (semicolon separated)");
|
||||
r_config_set_b (core->config, "r2ai.auto.yolo", false);
|
||||
r_config_desc (core->config, "r2ai.auto.yolo", "Execute potentially dangerous commands in auto mode without asking");
|
||||
r_config_set_b (core->config, "r2ai.auto.reset_on_query", false);
|
||||
r_config_desc (core->config, "r2ai.auto.reset_on_query", "Reset auto-mode conversation state on new user queries");
|
||||
r_config_set_b (core->config, "r2ai.chat.show_cost", true);
|
||||
// Configure HTTP timeout in seconds
|
||||
r_config_desc (core->config, "r2ai.chat.show_cost", "Display estimated API cost for chat interactions");
|
||||
|
||||
r_config_set_i (core->config, "r2ai.http.timeout", 120);
|
||||
// Configure HTTP rate limiting and retry parameters
|
||||
r_config_desc (core->config, "r2ai.http.timeout", "HTTP client timeout (seconds) for provider API calls");
|
||||
r_config_set_i (core->config, "r2ai.http.max_retries", 10);
|
||||
r_config_desc (core->config, "r2ai.http.max_retries", "Maximum number of HTTP retries for failed requests");
|
||||
r_config_set_i (core->config, "r2ai.http.max_backoff", 30);
|
||||
r_config_desc (core->config, "r2ai.http.max_backoff", "Maximum backoff time (seconds) between HTTP retries");
|
||||
r_config_set (core->config, "r2ai.http.backend", "auto"); // Options: auto, libcurl, socket, system
|
||||
r_config_desc (core->config, "r2ai.http.backend", "HTTP backend to use (auto, libcurl, socket, system)");
|
||||
r_config_set_b (core->config, "r2ai.http.use_files", false);
|
||||
r_config_desc (core->config, "r2ai.http.use_files", "Use temporary files to pass HTTP request/response payloads (true/false)");
|
||||
r_config_lock (core->config, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
static bool r2ai_fini(RCorePluginSession *cps) {
|
||||
RCore *core = cps->core;
|
||||
#else
|
||||
static int r2ai_fini(void *user, const char *input) {
|
||||
RCmd *cmd = (RCmd *)user;
|
||||
RCore *core = cmd->data;
|
||||
#endif
|
||||
r_config_lock (core->config, false);
|
||||
r_config_rm (core->config, "r2ai.api");
|
||||
r_config_rm (core->config, "r2ai.cmds");
|
||||
@@ -792,6 +994,8 @@ static int r2ai_fini(void *user, const char *input) {
|
||||
r_config_rm (core->config, "r2ai.http.timeout");
|
||||
r_config_rm (core->config, "r2ai.http.max_retries");
|
||||
r_config_rm (core->config, "r2ai.http.max_backoff");
|
||||
r_config_rm (core->config, "r2ai.http.backend");
|
||||
r_config_rm (core->config, "r2ai.http.use_files");
|
||||
r_config_lock (core->config, true);
|
||||
|
||||
// Free the conversation
|
||||
@@ -808,8 +1012,13 @@ static int r2ai_fini(void *user, const char *input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
static bool r_cmd_r2ai_client(RCorePluginSession *cps, const char *input) {
|
||||
RCore *core = cps->core;
|
||||
#else
|
||||
static int r_cmd_r2ai_client(void *user, const char *input) {
|
||||
RCore *core = (RCore *)user;
|
||||
#endif
|
||||
if (r_str_startswith (input, "r2ai")) {
|
||||
cmd_r2ai (core, r_str_trim_head_ro (input + 4));
|
||||
return true;
|
||||
@@ -823,7 +1032,7 @@ RCorePlugin r_core_plugin_r2ai_client = {
|
||||
.name = "r2ai",
|
||||
.desc = "r2ai plugin in plain C",
|
||||
.author = "pancake",
|
||||
.version = "0.9.4",
|
||||
.version = "1.1.1",
|
||||
.license = "MIT",
|
||||
},
|
||||
.init = r2ai_init,
|
||||
@@ -835,6 +1044,9 @@ RCorePlugin r_core_plugin_r2ai_client = {
|
||||
R_API RLibStruct radare_plugin = {
|
||||
.type = R_LIB_TYPE_CORE,
|
||||
.data = &r_core_plugin_r2ai_client,
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
.abiversion = R2_ABIVERSION,
|
||||
#endif
|
||||
.version = R2_VERSION
|
||||
};
|
||||
#endif
|
||||
@@ -6,6 +6,20 @@
|
||||
#include "r_vdb.h"
|
||||
#include "markdown.h"
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
#define R2_PRINTF(...) r_cons_printf (core->cons, __VA_ARGS__)
|
||||
#define R2_FLUSH() r_cons_flush (core->cons)
|
||||
#define R2_NEWLINE() r_cons_newline (core->cons)
|
||||
#define R2_PRINTLN(x) r_cons_println (core->cons, x)
|
||||
#define R2_INTERRUPTED() r_cons_is_breaked (core->cons)
|
||||
#else
|
||||
#define R2_PRINTF(...) r_cons_printf (__VA_ARGS__)
|
||||
#define R2_FLUSH() r_cons_flush ()
|
||||
#define R2_NEWLINE() r_cons_newline()
|
||||
#define R2_PRINTLN(x) r_cons_println(x)
|
||||
#define R2_INTERRUPTED() r_cons_is_breaked ()
|
||||
#endif
|
||||
|
||||
// Tool definition structure
|
||||
typedef struct {
|
||||
char *name;
|
||||
@@ -44,6 +58,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
const char *role;
|
||||
const char *content;
|
||||
const char *reasoning_content;
|
||||
const R2AI_ContentBlocks *content_blocks;
|
||||
const char *tool_call_id;
|
||||
const R2AI_ToolCall *tool_calls;
|
||||
@@ -51,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 {
|
||||
@@ -86,150 +101,176 @@ typedef struct {
|
||||
/**
|
||||
* Initialize a new empty messages array
|
||||
*/
|
||||
R_API R2AI_Messages *r2ai_msgs_new (void);
|
||||
R_API R2AI_Messages *r2ai_msgs_new(void);
|
||||
|
||||
/**
|
||||
* Initialize the conversation container (call during plugin init)
|
||||
*/
|
||||
R_API void r2ai_conversation_init (void);
|
||||
R_API void r2ai_conversation_init(void);
|
||||
|
||||
/**
|
||||
* Get the conversation instance (returns NULL if not initialized)
|
||||
*/
|
||||
R_API R2AI_Messages *r2ai_conversation_get (void);
|
||||
R_API R2AI_Messages *r2ai_conversation_get(void);
|
||||
|
||||
/**
|
||||
* Clear all messages in a container without freeing the container
|
||||
*/
|
||||
R_API void r2ai_msgs_clear (R2AI_Messages *msgs);
|
||||
R_API void r2ai_msgs_clear(R2AI_Messages *msgs);
|
||||
|
||||
/**
|
||||
* Add a message to the array
|
||||
* All strings are duplicated, so caller can free their copies
|
||||
*/
|
||||
R_API bool r2ai_msgs_add (R2AI_Messages *msgs, const R2AI_Message *msg);
|
||||
R_API bool r2ai_msgs_add(R2AI_Messages *msgs, const R2AI_Message *msg);
|
||||
|
||||
/**
|
||||
* Add a tool call to the last message in the array
|
||||
* All strings are duplicated, so caller can free their copies
|
||||
*/
|
||||
R_API bool r2ai_msgs_add_tool_call (R2AI_Messages *msgs, const R2AI_ToolCall *tc);
|
||||
R_API bool r2ai_msgs_add_tool_call(R2AI_Messages *msgs, const R2AI_ToolCall *tc);
|
||||
|
||||
/**
|
||||
* Parse a JSON response string and add the messages to the array
|
||||
* Returns true on success, false on failure
|
||||
*/
|
||||
R_API bool r2ai_msgs_from_response (R2AI_Messages *msgs, const char *json_str);
|
||||
R_API bool r2ai_msgs_from_response(R2AI_Messages *msgs, const char *json_str);
|
||||
|
||||
/**
|
||||
* Parse a RJson object directly and add the messages to the array
|
||||
* Returns true on success, false on failure
|
||||
*/
|
||||
R_API bool r2ai_msgs_from_json (R2AI_Messages *msgs, const RJson *json);
|
||||
R_API bool r2ai_msgs_from_json(R2AI_Messages *msgs, const RJson *json);
|
||||
|
||||
/**
|
||||
* Convert messages array to JSON string
|
||||
* Caller must free the returned string
|
||||
*/
|
||||
R_API char *r2ai_msgs_to_json (const R2AI_Messages *msgs);
|
||||
R_API char *r2ai_msgs_to_json(const R2AI_Messages *msgs);
|
||||
|
||||
/**
|
||||
* Convert messages array to Anthropic format JSON string
|
||||
* Caller must free the returned string
|
||||
*/
|
||||
R_API char *r2ai_msgs_to_anthropic_json (const R2AI_Messages *msgs);
|
||||
R_API char *r2ai_msgs_to_anthropic_json(const R2AI_Messages *msgs);
|
||||
|
||||
/**
|
||||
* Free a messages array and all associated data
|
||||
*/
|
||||
R_API void r2ai_msgs_free (R2AI_Messages *msgs);
|
||||
R_API void r2ai_msgs_free(R2AI_Messages *msgs);
|
||||
|
||||
/**
|
||||
* Free the conversation (call during plugin unload)
|
||||
*/
|
||||
R_API void r2ai_conversation_free (void);
|
||||
R_API void r2ai_conversation_free(void);
|
||||
|
||||
/**
|
||||
* Free a R2AI_Message structure
|
||||
*/
|
||||
R_API void r2ai_message_free (R2AI_Message *msg);
|
||||
R_API void r2ai_message_free(R2AI_Message *msg);
|
||||
|
||||
/**
|
||||
* Delete the last N messages from the message array
|
||||
* If n <= 0, defaults to deleting just the last message
|
||||
*/
|
||||
R_API void r2ai_delete_last_messages (R2AI_Messages *messages, int n);
|
||||
R_API void r2ai_delete_last_messages(R2AI_Messages *messages, int n);
|
||||
|
||||
/**
|
||||
* Get the global tools instance
|
||||
* Returns a pointer to the global tools structure
|
||||
*/
|
||||
R_API const R2AI_Tools *r2ai_get_tools (void);
|
||||
R_API const R2AI_Tools *r2ai_get_tools(void);
|
||||
|
||||
/**
|
||||
* Parse OpenAI format tools JSON into internal tools structure
|
||||
* Caller must free the result with r2ai_tools_free
|
||||
*/
|
||||
R_API R2AI_Tools *r2ai_tools_parse (const char *tools_json);
|
||||
R_API R2AI_Tools *r2ai_tools_parse(const char *tools_json);
|
||||
|
||||
/**
|
||||
* Convert tools structure to OpenAI format JSON
|
||||
* Caller must free the returned string
|
||||
*/
|
||||
R_API char *r2ai_tools_to_openai_json (const R2AI_Tools *tools);
|
||||
R_API char *r2ai_tools_to_openai_json(const R2AI_Tools *tools);
|
||||
|
||||
/**
|
||||
* Convert tools structure to Anthropic format JSON
|
||||
* Caller must free the returned string
|
||||
*/
|
||||
R_API char *r2ai_tools_to_anthropic_json (const R2AI_Tools *tools);
|
||||
R_API char *r2ai_tools_to_anthropic_json(const R2AI_Tools *tools);
|
||||
|
||||
/**
|
||||
* Free a tools structure and all associated data
|
||||
*/
|
||||
R_API void r2ai_tools_free (R2AI_Tools *tools);
|
||||
R_API void r2ai_tools_free(R2AI_Tools *tools);
|
||||
|
||||
/**
|
||||
* Execute a tool and return the output
|
||||
* Execute a tool and return the output
|
||||
* The command to execute is within args. The user may interactively
|
||||
* modify this command, hence edited_command contains the real
|
||||
* command which was run
|
||||
* Caller must free edited_command
|
||||
*/
|
||||
R_API char *execute_tool (RCore *core, const char *tool_name, const char *args);
|
||||
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, char **edited_command);
|
||||
|
||||
/**
|
||||
* Send an HTTP POST request
|
||||
*
|
||||
* @param core The RCore instance
|
||||
* @param url The URL to send the request to
|
||||
* @param headers Array of headers, NULL terminated
|
||||
* @param data The data to send in the request
|
||||
* @param code Pointer to store the response code
|
||||
* @param rlen Pointer to store the response length
|
||||
* @param use_files Use files instead of arguments
|
||||
* @return Response body as string (must be freed by caller) or NULL on error
|
||||
*/
|
||||
R_API char *r2ai_http_post (const char *url, const char *headers[], const char *data, int *code, int *rlen);
|
||||
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen);
|
||||
|
||||
/**
|
||||
* Send an HTTP GET request
|
||||
*
|
||||
* @param url The URL to send the request to
|
||||
* @param headers Array of headers, NULL terminated
|
||||
* @param code Pointer to store the response code
|
||||
* @param rlen Pointer to store the response length
|
||||
* @return Response body as string (must be freed by caller) or NULL on error
|
||||
*/
|
||||
R_API char *r2ai_http_get(const char *url, const char *headers[], int *code, int *rlen);
|
||||
|
||||
/**
|
||||
* Get the base URL for a given provider
|
||||
*
|
||||
* @param core RCore instance for configuration
|
||||
* @param provider The provider name (e.g., "openai", "anthropic", etc.)
|
||||
* @return Base URL for the provider, or NULL if unknown
|
||||
*/
|
||||
R_IPI const char *r2ai_get_provider_url(RCore *core, const char *provider);
|
||||
|
||||
// anthropic
|
||||
R_IPI R2AI_ChatResponse *r2ai_anthropic (RCore *core, R2AIArgs args);
|
||||
R_IPI R2AI_ChatResponse *r2ai_anthropic(RCore *core, R2AIArgs args);
|
||||
|
||||
// openai
|
||||
R_IPI R2AI_ChatResponse *r2ai_openai (RCore *core, R2AIArgs args);
|
||||
R_IPI R2AI_ChatResponse *r2ai_openai(RCore *core, R2AIArgs args);
|
||||
R_IPI void r2ai_openai_fini(void);
|
||||
|
||||
// auto mode
|
||||
R_IPI void cmd_r2ai_a (RCore *core, const char *user_query);
|
||||
R_IPI char *r2ai (RCore *core, R2AIArgs args);
|
||||
R_IPI void cmd_r2ai_a(RCore *core, const char *user_query);
|
||||
R_API char *r2ai(RCore *core, R2AIArgs args);
|
||||
|
||||
R_IPI R2AI_ChatResponse *r2ai_llmcall (RCore *core, R2AIArgs args);
|
||||
R_IPI R2AI_ChatResponse *r2ai_llmcall(RCore *core, R2AIArgs args);
|
||||
|
||||
R_IPI void cmd_r2ai_logs (RCore *core);
|
||||
R_IPI void cmd_r2ai_logs(RCore *core);
|
||||
|
||||
/**
|
||||
* Create a conversation with system prompt and optional user message
|
||||
*/
|
||||
R_API R2AI_Messages *create_conversation (const char *system_prompt, const char *user_message);
|
||||
R_API R2AI_Messages *create_conversation(const char *user_message);
|
||||
|
||||
/**
|
||||
* Process messages through LLM and handle tool calls recursively
|
||||
*/
|
||||
R_API void process_messages (RCore *core, R2AI_Messages *messages, const char *system_prompt, int n_run);
|
||||
R_API void process_messages(RCore *core, R2AI_Messages *messages, const char *system_prompt, int n_run);
|
||||
|
||||
/**
|
||||
* Helper function to convert RJson to string
|
||||
|
||||
@@ -20,7 +20,25 @@ static volatile sig_atomic_t r2ai_http_interrupted = 0;
|
||||
|
||||
// Signal handler for SIGINT
|
||||
static void r2ai_http_sigint_handler(int sig) {
|
||||
r2ai_http_interrupted = 1;
|
||||
(void)sig;
|
||||
r2ai_http_interrupted = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Portable install/restore helpers for SIGINT handler.
|
||||
* Some platforms may not have struct sigaction available; use signal()
|
||||
* as a fallback. We allocate storage for the old handler when
|
||||
* sigaction is available so the restore function can restore it later.
|
||||
*/
|
||||
static void install_sigint_handler_local(void **out_old, int *out_old_is_sigaction) {
|
||||
void (*old)(int) = signal (SIGINT, r2ai_http_sigint_handler);
|
||||
*out_old = (void *)old;
|
||||
*out_old_is_sigaction = 0;
|
||||
}
|
||||
|
||||
static void restore_sigint_handler_local(void *old, int old_is_sigaction) {
|
||||
(void)old_is_sigaction;
|
||||
signal (SIGINT, (void (*)(int))old);
|
||||
}
|
||||
|
||||
// Helper function to implement exponential backoff sleep
|
||||
@@ -126,12 +144,10 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
|
||||
}
|
||||
}
|
||||
|
||||
// Install signal handler for interruption
|
||||
struct sigaction new_action, old_action;
|
||||
new_action.sa_handler = r2ai_http_sigint_handler;
|
||||
sigemptyset (&new_action.sa_mask);
|
||||
new_action.sa_flags = 0;
|
||||
sigaction (SIGINT, &new_action, &old_action);
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
@@ -155,7 +171,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
sigaction (SIGINT, &old_action, NULL); // Restore signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
|
||||
return NULL;
|
||||
}
|
||||
response.data[0] = '\0';
|
||||
@@ -169,7 +185,7 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
sigaction (SIGINT, &old_action, NULL); // Restore signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -264,12 +280,285 @@ static char *curl_http_post(const char *url, const char *headers[], const char *
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // HAVE_LIBCURL
|
||||
|
||||
/**
|
||||
* Execute curl as a system command, sending data via temporary file
|
||||
*
|
||||
* @param url The URL to send the request to
|
||||
* @param headers Array of headers, NULL terminated
|
||||
* @param data The data to send in the request
|
||||
* @param code Pointer to store the response code
|
||||
* @param rlen Pointer to store the response length
|
||||
* @return Response body as string (must be freed by caller) or NULL on error
|
||||
*/
|
||||
static char *system_curl_post_file(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen, bool use_files) {
|
||||
if (!url || !headers || !data || !code) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int timeout = r_config_get_i (core->config, "r2ai.http.timeout");
|
||||
if (timeout <= 0) {
|
||||
timeout = 120; // Use default if invalid
|
||||
}
|
||||
|
||||
int max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
|
||||
if (max_retries < 0) {
|
||||
max_retries = 10; // Use default if invalid
|
||||
}
|
||||
|
||||
int max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
|
||||
if (max_backoff <= 0) {
|
||||
max_backoff = 30; // Use default if invalid
|
||||
}
|
||||
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
|
||||
// Retry loop
|
||||
char *result = NULL;
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
|
||||
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
|
||||
// Create a temporary file for the data
|
||||
char *temp_file = r_file_temp ("r2ai_data");
|
||||
if (!temp_file) {
|
||||
R_LOG_ERROR ("Failed to create temporary file for curl data");
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (use_files) {
|
||||
// Write data to the temporary file
|
||||
if (!r_file_dump (temp_file, (const ut8 *)data, strlen (data), 0)) {
|
||||
R_LOG_ERROR ("Failed to write data to temporary file");
|
||||
free (temp_file);
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compose curl command
|
||||
RStrBuf *cmd = r_strbuf_new ("curl -s");
|
||||
|
||||
// Add timeout
|
||||
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
|
||||
|
||||
// Add headers
|
||||
for (int i = 0; headers[i] != NULL; i++) {
|
||||
r_strbuf_appendf (cmd, " -H \"%s\"", headers[i]);
|
||||
}
|
||||
|
||||
if (use_files) {
|
||||
// Add data file with @ prefix for curl
|
||||
r_strbuf_appendf (cmd, " -X POST -d @%s \"%s\"", temp_file, url);
|
||||
} else {
|
||||
char *s = strdup (data);
|
||||
s = r_str_replace_all (s, "\\", "\\\\");
|
||||
// s = r_str_replace_all (s, "'", "\\'");
|
||||
s = r_str_replace_all (s, "'", "'\\''");
|
||||
r_strbuf_appendf (cmd, " -X POST -d '%s' \"%s\"", s, url);
|
||||
free (s);
|
||||
}
|
||||
|
||||
// Execute the curl command
|
||||
char *cmd_str = r_strbuf_drain (cmd);
|
||||
r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
|
||||
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (timeout + 10); // Add a little extra for process startup
|
||||
#endif
|
||||
|
||||
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
|
||||
char *response = r_sys_cmd_str (cmd_str, NULL, NULL);
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
free (cmd_str);
|
||||
r_file_rm (temp_file);
|
||||
free (temp_file);
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (response);
|
||||
response = NULL;
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We can't easily get the HTTP status code using this method
|
||||
// Let's assume 200 if we got a response, and 0 otherwise
|
||||
if (response) {
|
||||
*code = 200;
|
||||
if (rlen) {
|
||||
*rlen = strlen (response);
|
||||
}
|
||||
result = response;
|
||||
success = true;
|
||||
} else {
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
*code = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute curl as a system command for GET requests
|
||||
*
|
||||
* @param url The URL to send the request to
|
||||
* @param headers Array of headers, NULL terminated
|
||||
* @param code Pointer to store the response code
|
||||
* @param rlen Pointer to store the response length
|
||||
* @return Response body as string (must be freed by caller) or NULL on error
|
||||
*/
|
||||
static char *system_curl_get(const char *url, const char *headers[], int *code, int *rlen) {
|
||||
if (!url || !code) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get timeout and retry configuration from config if available
|
||||
int timeout = 120; // Default timeout in seconds
|
||||
int max_retries = 10; // Default max retries
|
||||
int max_backoff = 30; // Default max backoff in seconds
|
||||
|
||||
RCore *core = r_cons_singleton ()->user;
|
||||
if (core) {
|
||||
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
|
||||
if (timeout <= 0) {
|
||||
timeout = 120; // Use default if invalid
|
||||
}
|
||||
|
||||
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
|
||||
if (max_retries < 0) {
|
||||
max_retries = 10; // Use default if invalid
|
||||
}
|
||||
|
||||
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
|
||||
if (max_backoff <= 0) {
|
||||
max_backoff = 30; // Use default if invalid
|
||||
}
|
||||
}
|
||||
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
|
||||
// Retry loop
|
||||
char *result = NULL;
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
|
||||
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
|
||||
// Compose curl command
|
||||
RStrBuf *cmd = r_strbuf_new ("curl -s");
|
||||
|
||||
// Add timeout
|
||||
r_strbuf_appendf (cmd, " --connect-timeout %d --max-time %d", 10, timeout);
|
||||
|
||||
// Add headers
|
||||
if (headers) {
|
||||
for (int i = 0; headers[i] != NULL; i++) {
|
||||
r_strbuf_appendf (cmd, " -H \"%s\"", headers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add URL
|
||||
r_strbuf_appendf (cmd, " \"%s\"", url);
|
||||
|
||||
// Execute the curl command
|
||||
char *cmd_str = r_strbuf_drain (cmd);
|
||||
r_sys_setenv ("R2_CURL", "1"); // Ensure R2 uses system curl
|
||||
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (timeout + 10); // Add a little extra for process startup
|
||||
#endif
|
||||
|
||||
R_LOG_DEBUG ("Running system curl: %s", cmd_str);
|
||||
char *response = r_sys_cmd_str (cmd_str, NULL, NULL);
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
free (cmd_str);
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (response);
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We can't easily get the HTTP status code using this method
|
||||
// Let's assume 200 if we got a response, and 0 otherwise
|
||||
if (response) {
|
||||
*code = 200;
|
||||
if (rlen) {
|
||||
*rlen = strlen (response);
|
||||
}
|
||||
result = response;
|
||||
success = true;
|
||||
} else {
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
*code = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Socket implementation with interrupt handling and retry logic
|
||||
static char *socket_http_post_with_interrupt(const char *url, const char *headers[], const char *data, int *code, int *rlen) {
|
||||
// Get timeout and retry configuration from config if available
|
||||
@@ -295,12 +584,10 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
|
||||
}
|
||||
}
|
||||
|
||||
// Install signal handler for interruption
|
||||
struct sigaction new_action, old_action;
|
||||
new_action.sa_handler = r2ai_http_sigint_handler;
|
||||
sigemptyset (&new_action.sa_mask);
|
||||
new_action.sa_flags = 0;
|
||||
sigaction (SIGINT, &new_action, &old_action);
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
@@ -312,8 +599,10 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
|
||||
|
||||
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (timeout); // Use configured timeout
|
||||
#endif
|
||||
|
||||
// Make the request
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
@@ -323,7 +612,9 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
|
||||
#endif
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
@@ -366,18 +657,362 @@ static char *socket_http_post_with_interrupt(const char *url, const char *header
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
sigaction (SIGINT, &old_action, NULL);
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
R_API char *r2ai_http_post(const char *url, const char *headers[], const char *data, int *code, int *rlen) {
|
||||
#if HAVE_LIBCURL
|
||||
static char *curl_http_get(const char *url, const char *headers[], int *code, int *rlen) {
|
||||
if (!url || !code) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get timeout and retry configuration from config if available
|
||||
int timeout = 120; // Default timeout in seconds
|
||||
int max_retries = 10; // Default max retries
|
||||
int max_backoff = 30; // Default max backoff in seconds
|
||||
|
||||
RCore *core = r_cons_singleton ()->user;
|
||||
if (core) {
|
||||
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
|
||||
if (timeout <= 0) {
|
||||
timeout = 120; // Use default if invalid
|
||||
}
|
||||
|
||||
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
|
||||
if (max_retries < 0) {
|
||||
max_retries = 10; // Use default if invalid
|
||||
}
|
||||
|
||||
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
|
||||
if (max_backoff <= 0) {
|
||||
max_backoff = 30; // Use default if invalid
|
||||
}
|
||||
}
|
||||
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
|
||||
// Retry loop
|
||||
char *result = NULL;
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
|
||||
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
struct curl_slist *curl_headers = NULL;
|
||||
CurlResponse response = { 0 };
|
||||
|
||||
// Initialize response
|
||||
response.data = malloc (1);
|
||||
if (!response.data) {
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
|
||||
return NULL;
|
||||
}
|
||||
response.data[0] = '\0';
|
||||
response.size = 0;
|
||||
|
||||
curl = curl_easy_init ();
|
||||
if (!curl) {
|
||||
free (response.data);
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction); // Restore signal handler
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set URL
|
||||
curl_easy_setopt (curl, CURLOPT_URL, url);
|
||||
|
||||
// This is a GET request - no POST data
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L);
|
||||
|
||||
// Set headers if provided
|
||||
if (headers) {
|
||||
for (int i = 0; headers[i] != NULL; i++) {
|
||||
curl_headers = curl_slist_append (curl_headers, headers[i]);
|
||||
}
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, curl_headers);
|
||||
}
|
||||
|
||||
// Set write callback
|
||||
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *)&response);
|
||||
|
||||
// Set progress callback to handle interrupts
|
||||
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt (curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
||||
curl_easy_setopt (curl, CURLOPT_XFERINFODATA, NULL);
|
||||
|
||||
// Set timeout options
|
||||
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds connect timeout
|
||||
curl_easy_setopt (curl, CURLOPT_TIMEOUT, (long)timeout); // Use configured timeout
|
||||
|
||||
// Follow redirects
|
||||
curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
// Perform the request
|
||||
res = curl_easy_perform (curl);
|
||||
|
||||
// Check for interruption
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (response.data);
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
break; // Exit the retry loop
|
||||
}
|
||||
|
||||
// Get response code
|
||||
long http_code;
|
||||
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
*code = (int)http_code;
|
||||
|
||||
// Check for errors
|
||||
if (res != CURLE_OK) {
|
||||
R_LOG_ERROR ("curl_easy_perform () failed: %s", curl_easy_strerror (res));
|
||||
free (response.data);
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
|
||||
// Retry on network errors
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
|
||||
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
|
||||
if (http_code == 429 || (http_code >= 500 && http_code < 600)) {
|
||||
R_LOG_WARN ("Server returned %d response code", (int)http_code);
|
||||
free (response.data);
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after rate limiting...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
success = true;
|
||||
result = response.data;
|
||||
if (rlen) {
|
||||
*rlen = response.size;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
curl_slist_free_all (curl_headers);
|
||||
curl_easy_cleanup (curl);
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // HAVE_LIBCURL
|
||||
|
||||
// Socket implementation for GET requests
|
||||
static char *socket_http_get_with_interrupt(const char *url, const char *headers[], int *code, int *rlen) {
|
||||
// Get timeout and retry configuration from config if available
|
||||
int timeout = 120; // Default timeout in seconds
|
||||
int max_retries = 10; // Default max retries
|
||||
int max_backoff = 30; // Default max backoff in seconds
|
||||
|
||||
RCore *core = r_cons_singleton ()->user;
|
||||
if (core) {
|
||||
timeout = r_config_get_i (core->config, "r2ai.http.timeout");
|
||||
if (timeout <= 0) {
|
||||
timeout = 120; // Use default if invalid
|
||||
}
|
||||
|
||||
max_retries = r_config_get_i (core->config, "r2ai.http.max_retries");
|
||||
if (max_retries < 0) {
|
||||
max_retries = 10; // Use default if invalid
|
||||
}
|
||||
|
||||
max_backoff = r_config_get_i (core->config, "r2ai.http.max_backoff");
|
||||
if (max_backoff <= 0) {
|
||||
max_backoff = 30; // Use default if invalid
|
||||
}
|
||||
}
|
||||
|
||||
// Install signal handler for interruption (portable)
|
||||
void *r2ai_old_sig = NULL;
|
||||
int r2ai_old_is_sigaction = 0;
|
||||
install_sigint_handler_local(&r2ai_old_sig, &r2ai_old_is_sigaction);
|
||||
|
||||
// Reset interrupt flag
|
||||
r2ai_http_interrupted = 0;
|
||||
|
||||
// Retry loop
|
||||
char *result = NULL;
|
||||
int retry_count = 0;
|
||||
bool success = false;
|
||||
|
||||
while (!success && retry_count <= max_retries && !r2ai_http_interrupted) {
|
||||
// Set an alarm to limit the request time
|
||||
#if R2__UNIX__
|
||||
signal (SIGALRM, r2ai_http_sigint_handler);
|
||||
alarm (timeout); // Use configured timeout
|
||||
#endif
|
||||
|
||||
// Make the request - use r_socket_http_get if available
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
result = r_socket_http_get (url, headers, code, rlen);
|
||||
#else
|
||||
// For older radare2 versions, we might need to use a different approach
|
||||
// or create a custom implementation
|
||||
result = NULL;
|
||||
*code = 0;
|
||||
#endif
|
||||
|
||||
// Clear the alarm
|
||||
#if R2__UNIX__
|
||||
alarm (0);
|
||||
#endif
|
||||
|
||||
// Check if we were interrupted
|
||||
if (r2ai_http_interrupted) {
|
||||
R_LOG_DEBUG ("HTTP request was interrupted by user");
|
||||
free (result);
|
||||
result = NULL;
|
||||
break; // Exit the retry loop
|
||||
}
|
||||
|
||||
// Check for rate limiting or server errors (429, 500, 502, 503, 504)
|
||||
if (result && *code) {
|
||||
if (*code == 429 || (*code >= 500 && *code < 600)) {
|
||||
R_LOG_WARN ("Server returned %d response code", *code);
|
||||
free (result);
|
||||
result = NULL;
|
||||
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after error...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
}
|
||||
|
||||
// Check for other failures
|
||||
if (!result) {
|
||||
if (retry_count < max_retries) {
|
||||
retry_count++;
|
||||
R_LOG_INFO ("Retrying request (%d/%d) after failure...", retry_count, max_retries);
|
||||
r2ai_sleep_with_backoff (retry_count, max_backoff);
|
||||
continue;
|
||||
}
|
||||
break; // Exit the retry loop after max retries
|
||||
}
|
||||
|
||||
// If we get here, the request was successful
|
||||
success = true;
|
||||
}
|
||||
|
||||
// Restore the original signal handler
|
||||
restore_sigint_handler_local(r2ai_old_sig, r2ai_old_is_sigaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
R_API char *r2ai_http_post(RCore *core, const char *url, const char *headers[], const char *data, int *code, int *rlen) {
|
||||
const char *backend = "auto";
|
||||
bool use_files = false;
|
||||
|
||||
if (core) {
|
||||
const char *config_backend = r_config_get (core->config, "r2ai.http.backend");
|
||||
if (config_backend) {
|
||||
backend = config_backend;
|
||||
}
|
||||
use_files = r_config_get_b (core->config, "r2ai.http.use_files");
|
||||
}
|
||||
// Choose implementation based on backend config
|
||||
if (!strcmp (backend, "system")) {
|
||||
// Always use system curl with files for POST requests
|
||||
return system_curl_post_file (core, url, headers, data, code, rlen, use_files);
|
||||
} else if (!strcmp (backend, "libcurl")) {
|
||||
#if USE_LIBCURL && HAVE_LIBCURL
|
||||
return curl_http_post (url, headers, data, code, rlen);
|
||||
return curl_http_post (url, headers, data, code, rlen);
|
||||
#else
|
||||
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
|
||||
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
|
||||
#endif
|
||||
} else if (!strcmp (backend, "socket")) {
|
||||
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
|
||||
} else {
|
||||
// Auto-select best available implementation
|
||||
if (use_files) {
|
||||
// If use_files is true, always use system curl
|
||||
return system_curl_post_file (core, url, headers, data, code, rlen, use_files);
|
||||
} else {
|
||||
#if USE_LIBCURL && HAVE_LIBCURL
|
||||
return curl_http_post (url, headers, data, code, rlen);
|
||||
#else
|
||||
#if USE_R2_CURL
|
||||
r_sys_setenv ("R2_CURL", "1");
|
||||
r_sys_setenv ("R2_CURL", "1");
|
||||
#endif
|
||||
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
|
||||
return socket_http_post_with_interrupt (url, headers, data, code, rlen);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_API char *r2ai_http_get(const char *url, const char *headers[], int *code, int *rlen) {
|
||||
RCore *core = r_cons_singleton ()->user;
|
||||
const char *backend = "auto";
|
||||
|
||||
if (core) {
|
||||
backend = r_config_get (core->config, "r2ai.http.backend");
|
||||
}
|
||||
|
||||
// Choose implementation based on backend config
|
||||
if (!strcmp (backend, "system")) {
|
||||
// Use system curl for GET requests
|
||||
return system_curl_get (url, headers, code, rlen);
|
||||
} else if (!strcmp (backend, "libcurl")) {
|
||||
#if USE_LIBCURL && HAVE_LIBCURL
|
||||
return curl_http_get (url, headers, code, rlen);
|
||||
#else
|
||||
R_LOG_WARN ("LibCurl requested but not available, falling back to socket implementation");
|
||||
return socket_http_get_with_interrupt (url, headers, code, rlen);
|
||||
#endif
|
||||
} else if (!strcmp (backend, "socket")) {
|
||||
return socket_http_get_with_interrupt (url, headers, code, rlen);
|
||||
} else {
|
||||
// Auto-select best available implementation
|
||||
#if USE_LIBCURL && HAVE_LIBCURL
|
||||
return curl_http_get (url, headers, code, rlen);
|
||||
#else
|
||||
#if USE_R2_CURL
|
||||
r_sys_setenv ("R2_CURL", "1");
|
||||
#endif
|
||||
return socket_http_get_with_interrupt (url, headers, code, rlen);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <string.h>
|
||||
#include "r2ai.h"
|
||||
|
||||
// Define the radare2 command tool
|
||||
@@ -63,10 +64,6 @@ R_API R2AI_Tools *r2ai_tools_parse(const char *tools_json) {
|
||||
}
|
||||
|
||||
R2AI_Tools *tools = R_NEW0 (R2AI_Tools);
|
||||
if (!tools) {
|
||||
r_json_free (json);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int n_tools = json->children.count;
|
||||
tools->tools = R_NEWS0 (R2AI_Tool, n_tools);
|
||||
@@ -261,7 +258,7 @@ static char *to_cmd(const char *command) {
|
||||
return pj_drain (pj);
|
||||
}
|
||||
|
||||
R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output, char **edited_command) {
|
||||
if (!args) {
|
||||
return strdup ("{ \"res\":\"Command is NULL\" }");
|
||||
}
|
||||
@@ -277,8 +274,8 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
return strdup ("{ \"res\":\"You are already in r2!\" }");
|
||||
}
|
||||
|
||||
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.ask_to_execute");
|
||||
char *edited_command = NULL;
|
||||
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.yolo") != true;
|
||||
*edited_command = NULL; // keeps track of the command that was really executed in case user modifies it
|
||||
|
||||
if (ask_to_execute) {
|
||||
// Check if command contains newlines to determine if it's multi-line
|
||||
@@ -286,58 +283,67 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
|
||||
if (is_multiline) {
|
||||
// Use editor for multi-line commands
|
||||
edited_command = strdup (command);
|
||||
r_cons_editor (NULL, edited_command);
|
||||
command = edited_command;
|
||||
*edited_command = strdup (command);
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
r_cons_editor (core->cons, NULL, *edited_command);
|
||||
#else
|
||||
r_cons_editor (NULL, *edited_command);
|
||||
#endif
|
||||
command = *edited_command;
|
||||
} else {
|
||||
// For single-line commands, push the command to input buffer
|
||||
r_cons_newline ();
|
||||
r_cons_printf ("\x1b[31m[edit cmd]>\x1b[0m ");
|
||||
r_cons_flush ();
|
||||
R2_NEWLINE ();
|
||||
R2_PRINTF ("\x1b[31m[edit cmd]>\x1b[0m ");
|
||||
R2_FLUSH ();
|
||||
// Push the command to the input buffer
|
||||
r_cons_readpush (command, strlen (command));
|
||||
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
|
||||
|
||||
// Get user input with command pre-filled
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
r_cons_readpush (core->cons, command, strlen (command));
|
||||
r_cons_readpush (core->cons, "\x05", 1); // Ctrl+E - move to end
|
||||
const char *readline_result = r_line_readline (core->cons);
|
||||
#else
|
||||
r_cons_readpush (command, strlen (command));
|
||||
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
|
||||
const char *readline_result = r_line_readline ();
|
||||
|
||||
#endif
|
||||
// Check if interrupted or ESC pressed (readline_result is NULL or empty)
|
||||
if (r_cons_is_breaked () || !readline_result || !*readline_result) {
|
||||
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
|
||||
R_LOG_INFO ("Command execution cancelled %s", readline_result);
|
||||
return strdup ("R2AI_SIGINT");
|
||||
}
|
||||
|
||||
// Process the result
|
||||
if (readline_result && *readline_result) {
|
||||
edited_command = strdup (readline_result);
|
||||
command = edited_command;
|
||||
if (R_STR_ISNOTEMPTY (readline_result)) {
|
||||
*edited_command = strdup (readline_result);
|
||||
command = *edited_command;
|
||||
} else {
|
||||
// If user just pressed enter, keep the original command
|
||||
edited_command = strdup (command);
|
||||
command = edited_command;
|
||||
*edited_command = strdup (command);
|
||||
command = *edited_command;
|
||||
}
|
||||
}
|
||||
R_LOG_DEBUG ("Edited command: %s", *edited_command);
|
||||
} else {
|
||||
*edited_command = strdup (command);
|
||||
}
|
||||
|
||||
if (!hide_tool_output) {
|
||||
char *red_command = r_str_newf ("\x1b[31m%s\x1b[0m\n", command);
|
||||
r_cons_write (red_command, strlen (red_command));
|
||||
r_cons_flush ();
|
||||
char *red_command = r_str_newf ("\x1b[31m%s\x1b[0m\n", *edited_command);
|
||||
R2_PRINTF ("%s", red_command);
|
||||
R2_FLUSH ();
|
||||
free (red_command);
|
||||
}
|
||||
|
||||
char *json_cmd = to_cmd (command);
|
||||
char *json_cmd = to_cmd (*edited_command);
|
||||
if (!json_cmd) {
|
||||
free (edited_command); // Free edited_command if allocated
|
||||
// caller should free edited_command
|
||||
return strdup ("{ \"res\":\"Failed to create JSON command\" }");
|
||||
}
|
||||
|
||||
char *cmd_output = r_core_cmd_str (core, json_cmd);
|
||||
free (json_cmd);
|
||||
|
||||
// Free edited_command if we allocated it
|
||||
free (edited_command);
|
||||
|
||||
if (!cmd_output) {
|
||||
return strdup ("{ \"res\":\"Command returned no output or failed\" }");
|
||||
}
|
||||
@@ -345,11 +351,6 @@ R_API char *r2ai_r2cmd(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
return cmd_output;
|
||||
}
|
||||
|
||||
// R_API int r_base64_encode(char *bout, const ut8 *bin, int len);
|
||||
// R_API int r_base64_decode(ut8 *bout, const char *bin, int len);
|
||||
// R_API ut8 *r_base64_decode_dyn(const char *in, int len);
|
||||
// R_API char *r_base64_encode_dyn(const char *str, int len);
|
||||
|
||||
// qjs function implementation
|
||||
R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
if (!args) {
|
||||
@@ -365,7 +366,7 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
return strdup ("{ \"res\":\"Script value is NULL or empty\" }");
|
||||
}
|
||||
|
||||
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.ask_to_execute");
|
||||
bool ask_to_execute = r_config_get_b (core->config, "r2ai.auto.yolo") != true;
|
||||
const char *script = script_json->str_value;
|
||||
char *edited_script = NULL;
|
||||
|
||||
@@ -376,22 +377,29 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
if (is_multiline) {
|
||||
// Use editor for multi-line scripts
|
||||
edited_script = strdup (script);
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
r_cons_editor (core->cons, NULL, edited_script);
|
||||
#else
|
||||
r_cons_editor (NULL, edited_script);
|
||||
#endif
|
||||
script = edited_script;
|
||||
} else {
|
||||
// For single-line scripts, push the script to input buffer
|
||||
r_cons_printf ("\x1b[31m[edit js]>\x1b[0m ");
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("\x1b[31m[edit js]>\x1b[0m ");
|
||||
R2_FLUSH ();
|
||||
|
||||
// Push the script to the input buffer
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
r_cons_readpush (core->cons, script, strlen (script));
|
||||
r_cons_readpush (core->cons, "\x05", 1); // Ctrl+E - move to end
|
||||
const char *readline_result = r_line_readline (core->cons);
|
||||
#else
|
||||
r_cons_readpush (script, strlen (script));
|
||||
r_cons_readpush ("\x05", 1); // Ctrl+E - move to end
|
||||
|
||||
// Get user input with script pre-filled
|
||||
const char *readline_result = r_line_readline ();
|
||||
#endif
|
||||
|
||||
// Check if interrupted or ESC pressed (readline_result is NULL or empty)
|
||||
if (r_cons_is_breaked () || !readline_result || !*readline_result) {
|
||||
if (R2_INTERRUPTED () || R_STR_ISEMPTY (readline_result)) {
|
||||
free (edited_script); // Free if already allocated
|
||||
return strdup ("R2AI_SIGINT");
|
||||
}
|
||||
@@ -411,8 +419,8 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
if (!hide_tool_output) {
|
||||
char *print_script = r_str_newf ("\n```js\n%s\n```", script);
|
||||
char *print_script_rendered = r2ai_markdown (print_script);
|
||||
r_cons_write (print_script_rendered, strlen (print_script_rendered));
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("%s\n", print_script_rendered);
|
||||
R2_FLUSH ();
|
||||
free (print_script);
|
||||
free (print_script_rendered);
|
||||
}
|
||||
@@ -427,7 +435,11 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
return strdup ("{ \"res\":\"Failed to create script payload\" }");
|
||||
}
|
||||
|
||||
#if R2_VERSION_NUMBER >= 50909
|
||||
char *payload_base64 = r_base64_encode_dyn ((const ut8 *)payload, strlen (payload));
|
||||
#else
|
||||
char *payload_base64 = r_base64_encode_dyn ((const char *)payload, strlen (payload));
|
||||
#endif
|
||||
if (!payload_base64) {
|
||||
free (payload);
|
||||
return strdup ("{ \"res\":\"Failed to encode script\" }");
|
||||
@@ -451,7 +463,7 @@ R_API char *r2ai_qjs(RCore *core, RJson *args, bool hide_tool_output) {
|
||||
return cmd_output;
|
||||
}
|
||||
|
||||
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
|
||||
R_API char *execute_tool(RCore *core, const char *tool_name, const char *args, char **edited_command) {
|
||||
if (!tool_name || !args) {
|
||||
return strdup ("{ \"res\":\"Tool name or arguments are NULL\" }");
|
||||
}
|
||||
@@ -471,14 +483,14 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
|
||||
bool hide_tool_output = r_config_get_b (core->config, "r2ai.auto.hide_tool_output");
|
||||
|
||||
char *print_name = r_str_newf ("\x1b[1;32m\x1b[4m[%s]>\x1b[0m ", tool_name);
|
||||
r_cons_write (print_name, strlen (print_name));
|
||||
r_cons_flush ();
|
||||
R2_PRINTF ("%s\n", print_name);
|
||||
R2_FLUSH ();
|
||||
|
||||
free (print_name);
|
||||
char *tool_result = NULL;
|
||||
|
||||
if (strcmp (tool_name, "r2cmd") == 0) {
|
||||
tool_result = r2ai_r2cmd (core, args_json, hide_tool_output);
|
||||
tool_result = r2ai_r2cmd (core, args_json, hide_tool_output, edited_command);
|
||||
} else if (strcmp (tool_name, "execute_js") == 0) {
|
||||
tool_result = r2ai_qjs (core, args_json, hide_tool_output);
|
||||
} else {
|
||||
@@ -495,7 +507,7 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
|
||||
char *result = NULL;
|
||||
|
||||
// Check for empty or invalid response that could cause a crash
|
||||
if (!tool_result || !*tool_result) {
|
||||
if (R_STR_ISEMPTY (tool_result)) {
|
||||
free (tool_result);
|
||||
return strdup ("{ \"res\":\"Error: Empty or invalid response from QJS execution\" }");
|
||||
}
|
||||
@@ -508,9 +520,8 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
|
||||
return result;
|
||||
}
|
||||
|
||||
RJson *json = NULL;
|
||||
// Try to parse the JSON safely
|
||||
json = r_json_parse (tool_result);
|
||||
RJson *json = r_json_parse (tool_result);
|
||||
|
||||
if (!json) {
|
||||
// JSON parsing failed, return original content
|
||||
@@ -616,10 +627,10 @@ R_API char *execute_tool(RCore *core, const char *tool_name, const char *args) {
|
||||
return r_str_newf ("Error running tool: Unknown error\nCommand: %s", args);
|
||||
}
|
||||
if (!hide_tool_output) {
|
||||
r_cons_newline ();
|
||||
r_cons_write (result, strlen (result));
|
||||
r_cons_newline ();
|
||||
r_cons_flush ();
|
||||
R2_NEWLINE ();
|
||||
R2_PRINTF ("%s\n", result);
|
||||
R2_NEWLINE ();
|
||||
R2_FLUSH ();
|
||||
}
|
||||
|
||||
r_str_ansi_strip (result);
|
||||
|
||||