Compare commits

...
13 Commits
Author SHA1 Message Date
pancake 9077de9162 Snapshot async request configuration for HTTP sessions 2026-11-06 03:19:30 +01:00
pancake 287fad0ecd Release 1.5.4 2026-11-06 02:36:06 +01:00
pancake 08afde5117 Expand ~ in decai config paths before mkdir/touch 2026-11-02 19:23:04 +01:00
Agustin Torres a086a12f1d Replace the models api.anthropic.com retired
Replaced retired Anthropic model ids with the recommended replacements: the default model in src/anthropic.c and decai (claude-sonnet-5-5), plus docs, examples and model lists.

Notice anthropic-retired-models-2026-10 about api.anthropic.com's API describes the change. It was read from a file and is not signed by the provider.
2026-10-26 18:26:18 +01:00
pancake 4a86d0fc46 Fix build for r2-6.2.5 2026-10-20 23:21:46 +02:00
pancake 0509bf6755 Add decai -e http=curl|r2|auto 2026-10-19 08:54:40 +02:00
pancake 1d55b7f72a Define the new r2.http in the R2Pipe type for decai 2026-10-19 08:45:58 +02:00
pancake 222f0b7410 Exclude POSIX HTTP timers from WASI builds 2026-10-18 18:52:46 +02:00
pancake 065288e29f Use radare2 HTTP APIs for r2ai and decai in WASI 2026-10-18 18:52:46 +02:00
Abhi 5824aa4482 Release 1.5.3 2026-10-13 11:39:08 +05:30
Abhi 3c7ed851d8 Switch release assets to r2pm bindist zips (triplet naming)
- src/Makefile: bindist target producing r2ai-$(r2 -qv)-os-arch-bits.zip
  with plugins/, bin/, man/ and prompts/ (prompts dir overridable via
  R2AI_PROMPTSDIR; uname fallback for old r2pm)
- ci: split bindist job (linux x64/arm64 + macos) and windows bindist
  build (radare2 -qv for the zip name), attach zips + decai.r2.js to tag
  releases; no version pin so the action resolves the current release
2026-10-13 11:39:08 +05:30
pancake 1586d27e93 Release 1.5.2 2026-10-06 06:57:50 +02:00
pancake 898855d7ce Fix macOS build with inline r_mem_mem 2026-10-06 01:32:58 +02:00
30 changed files with 352 additions and 106 deletions

No files matched your search

+77 -29
View File
@@ -1,5 +1,8 @@
name: CI and Release
env:
R2V: 6.2.6
on:
push:
branches:
@@ -34,6 +37,8 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Build with Make
if: matrix.build_system == 'make'
@@ -56,39 +61,80 @@ jobs:
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
# Collect common binary artefact names produced by Make/Meson
# Include both "r2ai.*" and "libr2ai.*" variants for DLL/dylib/so on all platforms.
find src -type f \( -iname "r2ai.dll" -o -iname "libr2ai.dll" -o -iname "r2ai.dylib" -o -iname "libr2ai.dylib" -o -iname "r2ai.so" -o -iname "libr2ai.so" -o -iname "r2ai.exe" \) -exec cp -f {} release/ \; || true
# Also check top-level build output directories
find . -maxdepth 3 -type f \( -iname "r2ai.dll" -o -iname "libr2ai.dll" -o -iname "r2ai.dylib" -o -iname "libr2ai.dylib" -o -iname "r2ai.so" -o -iname "libr2ai.so" -o -iname "r2ai.exe" \) -exec cp -f {} 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
bindist:
name: Build r2pm binary (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Upload artifacts (tags only)
if: startsWith(github.ref, 'refs/tags/')
- name: Install radare2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Package r2pm binary
run: make -C src bindist
- name: Upload r2pm binary artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.os }}-${{ matrix.build_system }}-binary
path: release/r2ai-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.build_system }}.zip
name: bindist-${{ matrix.os }}
path: r2ai-*.zip
if-no-files-found: error
# - name: Smoke test
# run: |
# if command -v r2 >/dev/null 2>&1; then
# r2 -q -c 'r2ai -h' -- || true
# fi
windows-bindist:
name: Build r2pm binary (Windows)
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Meson/Ninja
run: |
python -m pip install --upgrade pip
pip install meson ninja
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Build with Meson
shell: powershell
run: |
cd src
$prefix = "${{ steps.r2.outputs.prefix }}"
meson setup build --backend ninja --prefix="$prefix" -Dr2_prefix="$prefix"
if ($LASTEXITCODE -ne 0) { meson configure build }
meson compile -C build
- name: Package r2pm binary
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path bindist\plugins,bindist\bin,bindist\prompts | Out-Null
Get-ChildItem src\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
Get-ChildItem src\..\build\ -Filter "*r2ai*.dll" -ErrorAction SilentlyContinue | Copy-Item -Destination bindist\plugins
Copy-Item src\build\r2ai.exe bindist\bin
Copy-Item prompts\*.r2ai.md bindist\prompts
$rv = (& radare2 -qv).Trim()
Compress-Archive -Path bindist\* -DestinationPath "r2ai-$rv-windows-x86-64.zip" -Force
- name: Upload r2pm binary artifact
uses: actions/upload-artifact@v7
with:
name: windows-bindist
path: r2ai-*.zip
if-no-files-found: error
release:
name: Create Release
needs: build
needs: [bindist, windows-bindist]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
@@ -105,10 +151,12 @@ jobs:
exit 0
fi
- name: Download artifacts
- name: Download r2pm binary artifacts
uses: actions/download-artifact@v8
with:
pattern: '*bindist*'
path: release-artifacts
merge-multiple: true
- name: Add decai.r2.js to release artifacts
run: cp decai/decai.r2.js release-artifacts/
@@ -117,6 +165,6 @@ jobs:
uses: softprops/action-gh-release@v3
with:
files: |
release-artifacts/**/*.zip
release-artifacts/*.zip
release-artifacts/decai.r2.js
generate_release_notes: true
generate_release_notes: true
+3
View File
@@ -12,3 +12,6 @@ venv
*.dylib
*.a
third_party
# r2pm binary packages
r2ai-*.zip
+3 -3
View File
@@ -23,12 +23,12 @@ if [ -f ~/.r2ai.openai-key ]; then
OPENAI_KEY=$(cat ~/.r2ai.openai-key)
fi
CLAUDE_KEY=""
CLAUDE_MODEL="claude-3-5-sonnet-20241022"
CLAUDE_MODEL="claude-sonnet-5-5"
if [ -f ~/.r2ai.anthropic-key ]; then
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
fi
DEEPSEEK_KEY=""
DEEPSEEK_MODEL="claude-3-5-sonnet-20241022"
DEEPSEEK_MODEL="claude-sonnet-5-5"
if [ -f ~/.r2ai.deepseek-key ]; then
DEEPSEEK_KEY=$(cat ~/.r2ai.anthropic-key)
fi
@@ -138,7 +138,7 @@ GEMINI_KEY=~/.r2ai-gemini.key
OPENAI_KEY=~/.r2ai-openai.key
CLAUDE_KEY=~/.r2ai-anthropic.key
DEEPSEEK_KEY=~/.r2ai-deepseek.key
CLAUDE_MODEL=claude-3-5-sonnet-20241022
CLAUDE_MODEL=claude-sonnet-5-5
EOF
exit 0
}
+1 -1
View File
@@ -49,7 +49,7 @@ python3 main.py
**Example selecting a remote models:**
```
[r2ai:0x00006aa0]> -m anthropic:claude-3-7-sonnet-20250219
[r2ai:0x00006aa0]> -m anthropic:claude-sonnet-5-5
[r2ai:0x00006aa0]> -m openai:gpt-4
```
+4 -7
View File
@@ -62,7 +62,7 @@ Uncensored:
Remote:
-m openapi:http://localhost:5001
-m openai:gpt-4
-m anthropic:claude-3-7-sonnet-20250219
-m anthropic:claude-sonnet-5-5
-m bedrock:anthropic.claude-3-5-sonnet-20241022-v1
-m kobaldcpp:http://localhost:5001
"""
@@ -80,12 +80,9 @@ OpenAI:
-m openai:gpt-4
-m openai:gpt-4-1106-preview
Anthropic:
-m anthropic:claude-2.1
-m anthropic:claude-3-haiku-20240307
-m anthropic:claude-3-opus-20240229
-m anthropic:claude-3-sonnet-20240229
-m anthropic:claude-3-5-sonnet-20241022
-m anthropic:claude-3-7-sonnet-20250219
-m anthropic:claude-opus-4-8
-m anthropic:claude-sonnet-5-5
-m anthropic:claude-haiku-4-5-20251001
groq:
-m groq:gemma-7b-it
-m groq:llama2-70b-4096
+1 -2
View File
@@ -170,8 +170,7 @@ def slurp_until(endword):
def list_models():
print("ollama/llama3:latest")
print("ollama/granite-code:8b")
print("anthropic/claude-3-5-sonnet-20240620")
print("anthropic/claude-3-7-sonnet-20250219")
print("anthropic/claude-sonnet-5-5")
print("openai/gpt-4o")
print("mistral/mistral-tiny")
+1 -1
View File
@@ -97,7 +97,7 @@ then you can type the commands you want to run when the r2ai plugin is loaded:
```console
r2ai -e api=anthropic
r2ai -e model=claude-3-7-sonnet-20250219
r2ai -e model=claude-sonnet-5-5
r2ai -e max_tokens=64000
```
+1 -1
View File
@@ -1 +1 @@
1.5.0
1.5.4
+24
View File
@@ -103,6 +103,12 @@ Persist user defaults in `~/.config/r2ai/decai.txt` with one `key=value` entry
per line, then edit it with `decai -E`. That file is loaded on the first
`decai` command in a session and reloaded after `decai -E` exits.
Use `decai -e http=curl` to force system curl, `http=r2` to require `r2.http()`,
or `http=auto` (the default) to use `r2.http()` when available and curl otherwise.
Failed `r2.http()` calls return an error without falling back to curl. The
`timeout` setting applies only to system curl. Save `http=curl` in
`~/.config/r2ai/decai.txt` to persist the override.
List possible APIs to discuss with AI: `decai -e api=?`:
```
@@ -212,3 +218,21 @@ void daemonize() {
}
...
```
## WASM HTTP
With radare2's WASI HTTP build, decai defaults to `r2.http()` to reach the existing
`r_socket_http_get/post` C APIs. Headers and JSON payloads go directly to the
host HTTP adapter; no shell curl, temporary payload file or WebSocket proxy is
needed. A failed/denied HTTP call is returned as an error, without a shell
fallback. In `http=auto` mode, runtimes without `r2.http()` use system curl.
Enable HTTP in the embedding application (`enabled: true` in JavaScript or
`network=True` in Python). The radare2 sandbox must permit both `socket` and
`network`; process execution is not needed. Browser requests remain subject
to CORS. The host adapter controls the HTTP deadline in WASM; decai's `timeout`
setting still controls native curl only. Responses are buffered, not streamed.
This requires radareorg/radare2#27037 and the rebuilt decai script. For the
C r2ai plugin compiled into WASM, the `auto` HTTP backend likewise selects
radare2's C HTTP implementation; native backend selection is unchanged.
+34 -32
View File
@@ -1,7 +1,7 @@
📦
27147 /main.js
27680 /main.js
✄
var te="1.5.0",b="decai",w="~/.config/r2ai",N=w+"/apikeys.txt",U=w+"/decai.txt",T="Rewrite this pseudocode into concise and clean code. Output only the provided function. Do not add wrappers, helper examples, test code, or main-like functions. Replace goto with structured control flow, simplify as much as possible, infer types and use better names for variables and parameters, some strings may be appearing as comments, preserve only what is implied by the input, and remove dead code.",L={decai:`# Using Decai
var te="1.5.4",b="decai",w="~/.config/r2ai",N=w+"/apikeys.txt",U=w+"/decai.txt",T="Rewrite this pseudocode into concise and clean code. Output only the provided function. Do not add wrappers, helper examples, test code, or main-like functions. Replace goto with structured control flow, simplify as much as possible, infer types and use better names for variables and parameters, some strings may be appearing as comments, preserve only what is implied by the input, and remove dead code.",L={decai:`# Using Decai
Decai is the radare2 plugin for decompiling functions with the help of language models.
@@ -26,6 +26,7 @@ These are the most recommended models for decompiling in local:
* 'decai -e apitype=chat|generate' choose the Ollama API endpoint (default: chat)
* 'decai -e headers=Authorization: Bearer ...\\nUser-Agent: curl/8.7.1' add or override HTTP headers
* "export DECAI_HEADERS='Authorization: Bearer ...\\nUser-Agent: curl/8.7.1'" set extra headers from the environment
* 'decai -e http=auto|r2|curl' select the HTTP transport (default: auto, prefers r2.http)
* 'decai -e timeout=0' disable curl timeouts, or set a positive number of seconds
* 'decai -e deterministic=true' to remove randomness from decompilation responses
@@ -117,53 +118,54 @@ Use radare2 to resolve user requests.
### Special cases
* On Swift binaries run...
`};var A={decopipe:{use:!1},host:"http://localhost",port:"11434",baseurl:"",extraHeaders:[],api:"ollama",pipeline:"",commands:"pdc",yolo:!1,tts:!1,language:"C",humanLanguage:"English",deterministic:!0,debug:!1,timeout:180,think:"",apitype:"chat",useFiles:!1,contextFile:"",model:"",cache:!1,maxInputTokens:-1,prompt:T,lastOutput:""},o={...A,decopipe:{...A.decopipe},extraHeaders:[...A.extraHeaders]};function ne(e){return(r2.cmd("-e dir.tmp").trim()??".")+"/"+e}function K(e,t){r2.cmd("'mkdir -p "+e),r2.cmd("'touch "+t)}function _(e){return(r2.cmd2("test -h").logs?.[0]?.message??"").includes("-fdx")?!0:r2.cmd("'test -vf "+e).startsWith("found")}function G(e,t){return e+" ".repeat(Math.max(0,t-e.length))}function h(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}function oe(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}function re(e){let t=e.match(/```json\s*([\s\S]*?)```/);return t&&t[1]?t[1].trim():e}function ie(e){let t=e,n=t.indexOf("{");n!==-1&&(t=t.slice(n));let r=t.lastIndexOf("}");return r!==-1&&(t=t.slice(0,r+1)),t}function $(e){return btoa(e)}function ae(e,t){let n=$(t);r2.cmd("p6ds "+n+" > "+e)}function k(e){let t=e;return t=t.replace(/<think>[\s\S]*?<\/think>/gi,""),t.split(`
`};var E={decopipe:{use:!1},host:"http://localhost",port:"11434",baseurl:"",extraHeaders:[],api:"ollama",pipeline:"",commands:"pdc",yolo:!1,tts:!1,language:"C",humanLanguage:"English",deterministic:!0,debug:!1,timeout:180,http:"auto",think:"",apitype:"chat",useFiles:!1,contextFile:"",model:"",cache:!1,maxInputTokens:-1,prompt:T,lastOutput:""},r={...E,decopipe:{...E.decopipe},extraHeaders:[...E.extraHeaders]};function re(e){return(r2.cmd("-e dir.tmp").trim()??".")+"/"+e}function ne(e){if(!e.startsWith("~/"))return e;let t=r2.cmd("'%HOME").trim()||r2.cmd("'%USERPROFILE").trim();return t?t+e.slice(1):e}function H(e,t){r2.cmd("'mkdir -p "+ne(e)),r2.cmd("'touch "+ne(t))}function _(e){return(r2.cmd2("test -h").logs?.[0]?.message??"").includes("-fdx")?!0:r2.cmd("'test -vf "+e).startsWith("found")}function G(e,t){return e+" ".repeat(Math.max(0,t-e.length))}function h(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}function oe(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}function ie(e){let t=e.match(/```json\s*([\s\S]*?)```/);return t&&t[1]?t[1].trim():e}function ae(e){let t=e,n=t.indexOf("{");n!==-1&&(t=t.slice(n));let o=t.lastIndexOf("}");return o!==-1&&(t=t.slice(0,o+1)),t}function $(e){return btoa(e)}function se(e,t){let n=$(t);r2.cmd("p6ds "+n+" > "+e)}function k(e){let t=e;return t=t.replace(/<think>[\s\S]*?<\/think>/gi,""),t.split(`
`).filter(n=>!n.trim().startsWith("```")).join(`
`)}function J(e){o.debug&&console.log(e)}function se(e){let t={};for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let[i,...a]=r.split("=");if(!i||a.length===0)continue;let s=a.join("=").trim(),l=i.toLowerCase().replace(/_api_key$/i,"");t[l]=s}return t}var Re=["DECAI_HEADERS","R2AI_HEADERS"];function Ue(e){let t=r2.cmd("'%"+e).trim();return t!==""&&!t.includes("=")?t:""}function _e(e){return e.trim().toLowerCase()}function Me(e){let t=e.indexOf(":"),n=e.indexOf("="),r=t!==-1&&(n===-1||t<n)?t:n;if(r===-1)return null;let i=e.slice(0,r).trim();return i===""?null:{name:i,value:e.slice(r+1).trim()}}function Ne(e){return e.value===""?`${e.name}:`:`${e.name}: ${e.value}`}function le(e,t){for(let n of e){let r=Me(n.trim());r&&t.set(_e(r.name),Ne(r))}}function Y(e){let t=new Map,n=e.replace(/\\n/g,`
`).split(/\r?\n/g).filter(r=>{let i=r.trim();return i&&!i.startsWith("#")});return le(n,t),Array.from(t.values())}function ce(e){return e.join("\\n")}function E(...e){let t=new Map;for(let n of e)le(n,t);return Array.from(t.values())}function Te(){for(let e of Re){let t=Ue(e);if(t)return Y(t)}return[]}function ue(){return E(Te(),o.extraHeaders)}function M(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function Ke(e){let{method:t,url:n,headers:r,payload:i}=e,a=["curl","-s"];if(o.timeout>0&&a.push("--max-time",String(o.timeout)),E(["Content-Type: application/json"],r).forEach(l=>a.push("-H",M(l))),t==="POST"){if(!i)throw new Error("Payload required for POST requests");let l=r2.fdump(i);a.push("--data-binary","@"+M(l),M(n));let u=a.join(" ")+"; rm "+M(l);return J(u),r2.syscmds(u)}else{a.push(M(n));let l=a.join(" ");return J(l),r2.syscmds(l)}}function qe(e){let t=e.split(`
`).filter(i=>i.trim()!=="");if(t.length<=1)return null;let n="",r=null;for(let i of t)try{let a=JSON.parse(i);if(a.error)return a;a.message?.content!==void 0&&(n+=a.message.content),r=a}catch{return null}return r?(r.message={role:"assistant",content:n},r):null}function de(e){try{let t=Ke(e).trim();if(t==="")return{error:"empty response"};try{return JSON.parse(t)}catch{let n=qe(t);return n||{error:t,rawOutput:t}}}catch(t){return{error:t.message}}}function O(e,t){return de({method:"GET",url:e,headers:t})}function pe(e,t,n){return de({method:"POST",url:e,headers:t,payload:n})}function z(e){return e.split("_")[0].toLowerCase()}function fe(e){if(!e.authKey)return;let t=Array.from(new Set([e.keyName,z(e.authKey)].filter(Boolean))),n;for(let r of t){let i=q(r,e.authKey);if(i[0])return i;!n&&i[2]!=="nope"&&(n=i)}return n||q(z(e.authKey),e.authKey)}function S(e){if(e)return typeof e=="string"?e:e.message||JSON.stringify(e)}function me(e){return o.baseurl||e.defaultBaseUrl}function he(e,t=ue()){return E(e,t)}function He(e){return e?["Authorization: Bearer "+e]:[]}function De(e){let t=["anthropic-version: 2023-06-01"];return e?E(t,["x-api-key: "+e]):t}function ge(e,t){switch(e.authStyle||"none"){case"bearer":return He(t);case"anthropic":return De(t);case"none":default:return[]}}function je(e,t,n){let r=O(e,t),i=S(r.error);return i?(console.error(i),"error invalid response"):r.data?.map(n).join(`
`)||""}function Be(e,t){let n=new Set;return e.filter(r=>{let i=t(r);return n.has(i)?!1:(n.add(i),!0)})}function V(e){let t=fe(e)?.[0]||null;return{baseUrl:me(e),headers:he(ge(e,t))}}function Fe(e){let{baseUrl:t,headers:n}=V(e),r=h(t,"v1/models");return je(r,n,i=>i.id)}function Le(e){let{baseUrl:t,headers:n}=V(e),r=O(h(t,"api/tags"),n),i=S(r.error);return i?(console.error(i),"error invalid response"):r.models?.map(a=>a.name).join(`
`)||""}function Ge(e){let{baseUrl:t,headers:n}=V(e),r=O(h(t,"v1/models"),n);return r.data?Be(r.data,i=>i.name||i.id).map(i=>[G(i.name||i.id,30),G(""+(i.max_context_length||""),10),i.description||""].join(" ")).join(`
`):""}var $e={openai:{buildPayload:(e,t)=>({stream:!1,model:e,messages:[{role:"user",content:t}]}),parseResponse:e=>{if(e.error)throw new Error(S(e.error)||"Unknown error");if(e.choices&&e.choices[0]?.message?.content)return k(e.choices[0].message.content);throw new Error("Invalid response format")},buildUrl:e=>h(e,"v1/chat/completions")},anthropic:{buildPayload:(e,t)=>{let n={model:e,max_tokens:5128,messages:[{role:"user",content:t}]};return W()&&(n.thinking={type:"enabled",budget_tokens:4096},n.max_tokens=16e3),o.deterministic&&!W()&&Object.assign(n,{temperature:0,top_p:0,top_k:1}),n},parseResponse:e=>{if(e.content&&Array.isArray(e.content)){let t=[];for(let n of e.content)n.text&&t.push(n.text);if(t.length>0)return k(t.join(`
`))}throw e.error?new Error(S(e.error)||"Unknown error"):new Error("Invalid response format")},buildUrl:e=>h(e,"v1/messages")},ollama:{buildPayload:(e,t)=>{let n={stream:!1,model:e};return o.apitype==="generate"?n.prompt=t:n.messages=[{role:"user",content:t}],W()?o.think==="true"||o.think==="1"?n.think=!0:n.think=o.think:n.think=!1,o.deterministic&&(n.options={repeat_last_n:0,top_p:1,top_k:1,temperature:0,repeat_penalty:1,seed:123}),n},parseResponse:e=>{if(e.error)throw new Error(S(e.error));if(typeof e.response=="string")return k(e.response);if(e.message?.content)return k(e.message.content);throw new Error(JSON.stringify(e))},buildUrl:e=>h(e,"api/"+o.apitype)},gemini:{buildPayload:(e,t)=>{let n={contents:[{parts:[{text:t}]}]},r={};return o.deterministic&&Object.assign(r,{temperature:0,topP:1,topK:1}),o.think!==""&&(X()?r.thinkingConfig={thinkingBudget:0}:r.thinkingConfig={thinkingBudget:8192}),Object.keys(r).length>0&&(n.generationConfig=r),n},parseResponse:e=>{if(e.candidates&&e.candidates[0]?.content?.parts){let n=e.candidates[0].content.parts.filter(r=>!r.thought&&r.text).map(r=>r.text);if(n.length>0)return k(n.join(`
`))}throw e.error?new Error(S(e.error)):(console.log(JSON.stringify(e)),new Error("Invalid response format"))},buildUrl:(e,t,n)=>h(e,`v1beta/models/${t}:generateContent?key=${n}`),requiresUrlApiKey:!0}},H={anthropic:{defaultModel:"claude-3-7-sonnet-20250219",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"anthropic",authStyle:"anthropic",apiStyle:"anthropic"},claude:{defaultModel:"claude-3-7-sonnet-20250219",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"claude",authStyle:"anthropic",apiStyle:"anthropic"},openai:{defaultModel:"gpt-4o-mini",defaultBaseUrl:"https://api.openai.com",authKey:"OPENAI_API_KEY",authStyle:"bearer",apiStyle:"openai"},ollama:{defaultModel:"qwen2.5-coder:latest",defaultBaseUrl:"http://localhost:11434",authStyle:"none",apiStyle:"ollama"},ollamacloud:{defaultModel:"gpt-oss:120b",defaultBaseUrl:"https://ollama.com",authKey:"OLLAMA_API_KEY",keyName:"ollamacloud",authStyle:"bearer",apiStyle:"ollama"},zen:{defaultModel:"big-pickle",defaultBaseUrl:"https://opencode.ai/zen",authKey:"OPENCODE_API_KEY",keyName:"zen",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["big-pickle","glm-5","kimi-k2.5"]},gemini:{defaultModel:"gemini-2.5-flash",defaultBaseUrl:"https://generativelanguage.googleapis.com",authKey:"GEMINI_API_KEY",authStyle:"none",apiStyle:"gemini",hardcodedModels:["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite"]},mistral:{defaultModel:"codestral-latest",defaultBaseUrl:"https://api.mistral.ai",authKey:"MISTRAL_API_KEY",authStyle:"bearer",apiStyle:"openai"},xai:{defaultModel:"grok-beta",defaultBaseUrl:"https://api.x.ai",authKey:"XAI_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["grok-2","grok-beta"]},lmstudio:{defaultModel:"local-model",defaultBaseUrl:"http://127.0.0.1:1234",authStyle:"none",apiStyle:"openai"},deepseek:{defaultModel:"deepseek-coder",defaultBaseUrl:"https://api.deepseek.com",authKey:"DEEPSEEK_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["deepseek-coder","deepseek-chat"]},openrouter:{defaultModel:"openai/gpt-4o-mini",defaultBaseUrl:"https://openrouter.ai/api",authKey:"OPENROUTER_API_KEY",authStyle:"bearer",apiStyle:"openai"},groq:{defaultModel:"llama-3.3-70b-versatile",defaultBaseUrl:"https://api.groq.com/openai",authKey:"GROQ_API_KEY",authStyle:"bearer",apiStyle:"openai"}};function D(e){return H[e]}function Q(){return Object.keys(H)}function ye(e){let t=D(e);if(!t)throw new Error(`Unknown provider: ${e}`);if(e==="mistral")return Ge(t);switch(t.apiStyle){case"openai":case"anthropic":return Fe(t);case"ollama":return Le(t);case"gemini":return"";default:return""}}function X(){return o.think==="false"||o.think==="0"}function W(){return o.think!==""&&!X()}function Je(e,t,n){let r=e;return o.think!==""&&n==="openai"&&(X()?(r+=' Answers directly and concisely, without showing any thinking steps or internal reasoning. Never include phrases like "Let me think".',r+=" /no_think"):r="Think step by step and explain the reasoning process, When answering, first output your reasoning inside <think> and </think> tags, then give the final answer."+r),t?r:o.prompt+Ye()+r}function Ye(){return`
.Translate the code into `+o.language+` programming language
`}function We(e,t,n,r){let i=o.model||e.defaultModel,a=Je(n,r,e.apiStyle),s=fe(e);if(t.requiresUrlApiKey&&s?.[1]&&e.authKey)return`Cannot read ~/.r2ai.${z(e.authKey)}-key`;let l=t.buildPayload(i,a),u=ge(e,s?.[0]||null);e.apiStyle==="ollama"&&u.push("Accept: application/x-ndjson");let v=he(u),c=t.buildUrl(me(e),i,s?.[0]||void 0);try{return t.parseResponse(pe(c,v,JSON.stringify(l)))}catch(d){return"ERROR: "+d.message}}function be(e,t){let n=D(o.api);return n?We(n,$e[n.apiStyle],e,t):`Unknown value for 'decai -e api'. Available: ${Q().join(", ")}`}function ze(){return Object.fromEntries(Object.entries(H).filter(([,e])=>e.authKey).map(([e,t])=>[e,t.authKey]))}function q(e,t){let n=r2.cmd("'%"+t).trim();if(!n.includes("=")&&n!=="")return[n.trim(),null,"env"];let r=e.toLowerCase(),i=N;if(_(i)){let s=r2.cmd("'cat "+i),l=se(s);if(r in l)return[l[r],null,"txt"]}let a="~/.r2ai."+r+"-key";if(_(a)){let s=r2.cmd("'cat "+a);return s===""?[null,"Cannot read "+a,"no"]:[s.trim(),null,"file"]}return[null,"Not available","nope"]}function ke(){K(w,N),r2.cmd("'ed "+N)}function Z(){Object.entries(ze()).forEach(([e,t])=>{let n=q(e,t)[2];console.log(n," ",e)})}function xe(e){let t=D(e);if(!t){console.error(`Unknown provider: ${e}`);return}try{let n=ye(e);n&&console.log(n),t.hardcodedModels&&t.hardcodedModels.forEach(r=>console.log(r)),e==="mistral"&&console.log("ministral-8b-latest"),!n&&!t.hardcodedModels&&console.log(t.defaultModel)}catch(n){let r=n;console.error(`Error listing models for ${e}:`,r.message),console.log(t.defaultModel)}}var ee=!1;function Ve(e){return e==="true"||e==="1"}function P(e){let t=o;return{get:()=>t[e],set:n=>{t[e]=Ve(n)}}}function C(e){let t=o;return{get:()=>t[e],set:n=>{t[e]=n}}}function Qe(e){let t=e.trim();return t==="chat"?"chat":t==="generate"?"generate":null}var ve={pipeline:{get:()=>o.pipeline,set:e=>{o.pipeline=e;try{o.decopipe=JSON.parse(r2.cmd("cat "+e))}catch(t){console.error(t)}}},model:{get:()=>o.model,set:e=>{e==="?"?xe(o.api):o.model=e.trim()}},deterministic:P("deterministic"),files:P("useFiles"),think:{get:()=>o.think||"false",set:e=>{o.think=e}},apitype:{get:()=>o.apitype,set:e=>{let t=Qe(e);t?o.apitype=t:console.error("Invalid apitype. Use chat or generate.")}},debug:P("debug"),timeout:{get:()=>o.timeout,set:e=>{o.timeout=Math.max(0,parseInt(e,10)||0)}},api:{get:()=>o.api,set:e=>{e==="?"?console.error(Q().join(`
`)):o.api=e}},lang:C("language"),hlang:C("humanLanguage"),cache:P("cache"),cmds:C("commands"),tts:P("tts"),yolo:P("yolo"),prompt:C("prompt"),ctxfile:C("contextFile"),baseurl:C("baseurl"),headers:{get:()=>ce(o.extraHeaders),set:e=>{o.extraHeaders=Y(e)}},maxtokens:{get:()=>o.maxInputTokens,set:e=>{o.maxInputTokens=parseInt(e,10)||-1}}};function Xe(){let e={host:o.host,port:o.port,lastOutput:o.lastOutput};Object.assign(o,A,e),o.decopipe={...A.decopipe},o.extraHeaders=[...A.extraHeaders]}function Ze(e){let t=e.trim();return!t||t.startsWith("#")?null:t.startsWith("decai -e ")?t.slice(9).trim():t}function x(e){let t=e.indexOf("="),n=t===-1?e:e.slice(0,t),r=t===-1?void 0:e.slice(t+1),i=ve[n];if(!i){console.error("Unknown config key");return}typeof r<"u"?i.set(r):console.log(i.get())}function Ae(){Object.entries(ve).forEach(([e,t])=>{let n=t.get();console.log("decai -e "+e+"="+n)})}function we(){if(Xe(),!_(U))return;let e=r2.call("cat "+U);for(let t of e.split(/\r?\n/)){let n=Ze(t);n&&n[0]!="#"&&x(n)}}function Ee(){ee||(we(),ee=!0)}function Oe(){K(w,U),r2.cmd("'ed "+U),we(),ee=!0}function p(e,t,n=!1){let r=(t||"").replace(/`/g,""),i=e.replace(/'/g,"");if(o.api==="r2"||o.api==="r2ai"){let s=ne(".pdc.txt");ae(s,r);let l=i.startsWith("-")?i:["-i",s,i].join(" "),u=o.baseurl||o.host+":"+o.port,v=h(u,"cmd/"+encodeURIComponent(l)),c=O(v,[]);return c.error?`Error: ${c.error}`:c.result||JSON.stringify(c)||"Cannot curl, use r2ai-server or r2ai -w"}if(i.startsWith("-"))return"";let a=i+`:
`+r;return o.maxInputTokens>0&&a.length>o.maxInputTokens&&(a=a.slice(0,o.maxInputTokens)),be(a,n)}function Pe(e){let t=r2.cmd("e scr.color");r2.cmd("e scr.color=0");try{return e()}finally{r2.cmd("e scr.color="+t)}}function Se(){let e="",t=n=>e+=" "+b+" "+n+`
`)}function J(e){r.debug&&console.log(e)}function le(e){let t={};for(let n of e.split(/\r?\n/)){let o=n.trim();if(!o||o.startsWith("#"))continue;let[i,...a]=o.split("=");if(!i||a.length===0)continue;let s=a.join("=").trim(),l=i.toLowerCase().replace(/_api_key$/i,"");t[l]=s}return t}var Ue=["DECAI_HEADERS","R2AI_HEADERS"];function _e(e){let t=r2.cmd("'%"+e).trim();return t!==""&&!t.includes("=")?t:""}function Me(e){return e.trim().toLowerCase()}function Ne(e){let t=e.indexOf(":"),n=e.indexOf("="),o=t!==-1&&(n===-1||t<n)?t:n;if(o===-1)return null;let i=e.slice(0,o).trim();return i===""?null:{name:i,value:e.slice(o+1).trim()}}function Te(e){return e.value===""?`${e.name}:`:`${e.name}: ${e.value}`}function ce(e,t){for(let n of e){let o=Ne(n.trim());o&&t.set(Me(o.name),Te(o))}}function W(e){let t=new Map,n=e.replace(/\\n/g,`
`).split(/\r?\n/g).filter(o=>{let i=o.trim();return i&&!i.startsWith("#")});return ce(n,t),Array.from(t.values())}function ue(e){return e.join("\\n")}function A(...e){let t=new Map;for(let n of e)ce(n,t);return Array.from(t.values())}function He(){for(let e of Ue){let t=_e(e);if(t)return W(t)}return[]}function de(){return A(He(),r.extraHeaders)}function M(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function Ke(e){let{method:t,url:n,headers:o,payload:i}=e,a=A(["Content-Type: application/json"],o);if(r.http!=="curl"){if(r2.http)return r2.http(t,n,a.join(`
`),i??"").body;if(r.http==="r2")throw new Error("r2.http is not available in this runtime")}let s=["curl","-s"];if(r.timeout>0&&s.push("--max-time",String(r.timeout)),a.forEach(l=>s.push("-H",M(l))),t==="POST"){if(!i)throw new Error("Payload required for POST requests");let l=r2.fdump(i);s.push("--data-binary","@"+M(l),M(n));let u=s.join(" ")+"; rm "+M(l);return J(u),r2.syscmds(u)}else{s.push(M(n));let l=s.join(" ");return J(l),r2.syscmds(l)}}function qe(e){let t=e.split(`
`).filter(i=>i.trim()!=="");if(t.length<=1)return null;let n="",o=null;for(let i of t)try{let a=JSON.parse(i);if(a.error)return a;a.message?.content!==void 0&&(n+=a.message.content),o=a}catch{return null}return o?(o.message={role:"assistant",content:n},o):null}function pe(e){try{let t=Ke(e).trim();if(t==="")return{error:"empty response"};try{return JSON.parse(t)}catch{let n=qe(t);return n||{error:t,rawOutput:t}}}catch(t){return{error:t.message}}}function O(e,t){return pe({method:"GET",url:e,headers:t})}function fe(e,t,n){return pe({method:"POST",url:e,headers:t,payload:n})}function z(e){return e.split("_")[0].toLowerCase()}function me(e){if(!e.authKey)return;let t=Array.from(new Set([e.keyName,z(e.authKey)].filter(Boolean))),n;for(let o of t){let i=K(o,e.authKey);if(i[0])return i;!n&&i[2]!=="nope"&&(n=i)}return n||K(z(e.authKey),e.authKey)}function S(e){if(e)return typeof e=="string"?e:e.message||JSON.stringify(e)}function he(e){return r.baseurl||e.defaultBaseUrl}function ge(e,t=de()){return A(e,t)}function De(e){return e?["Authorization: Bearer "+e]:[]}function je(e){let t=["anthropic-version: 2023-06-01"];return e?A(t,["x-api-key: "+e]):t}function ye(e,t){switch(e.authStyle||"none"){case"bearer":return De(t);case"anthropic":return je(t);case"none":default:return[]}}function Be(e,t,n){let o=O(e,t),i=S(o.error);return i?(console.error(i),"error invalid response"):o.data?.map(n).join(`
`)||""}function Fe(e,t){let n=new Set;return e.filter(o=>{let i=t(o);return n.has(i)?!1:(n.add(i),!0)})}function V(e){let t=me(e)?.[0]||null;return{baseUrl:he(e),headers:ge(ye(e,t))}}function Le(e){let{baseUrl:t,headers:n}=V(e),o=h(t,"v1/models");return Be(o,n,i=>i.id)}function Ge(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"api/tags"),n),i=S(o.error);return i?(console.error(i),"error invalid response"):o.models?.map(a=>a.name).join(`
`)||""}function $e(e){let{baseUrl:t,headers:n}=V(e),o=O(h(t,"v1/models"),n);return o.data?Fe(o.data,i=>i.name||i.id).map(i=>[G(i.name||i.id,30),G(""+(i.max_context_length||""),10),i.description||""].join(" ")).join(`
`):""}var Je={openai:{buildPayload:(e,t)=>({stream:!1,model:e,messages:[{role:"user",content:t}]}),parseResponse:e=>{if(e.error)throw new Error(S(e.error)||"Unknown error");if(e.choices&&e.choices[0]?.message?.content)return k(e.choices[0].message.content);throw new Error("Invalid response format")},buildUrl:e=>h(e,"v1/chat/completions")},anthropic:{buildPayload:(e,t)=>{let n={model:e,max_tokens:5128,messages:[{role:"user",content:t}]};return Y()&&(n.thinking={type:"enabled",budget_tokens:4096},n.max_tokens=16e3),r.deterministic&&!Y()&&Object.assign(n,{temperature:0,top_p:0,top_k:1}),n},parseResponse:e=>{if(e.content&&Array.isArray(e.content)){let t=[];for(let n of e.content)n.text&&t.push(n.text);if(t.length>0)return k(t.join(`
`))}throw e.error?new Error(S(e.error)||"Unknown error"):new Error("Invalid response format")},buildUrl:e=>h(e,"v1/messages")},ollama:{buildPayload:(e,t)=>{let n={stream:!1,model:e};return r.apitype==="generate"?n.prompt=t:n.messages=[{role:"user",content:t}],Y()?r.think==="true"||r.think==="1"?n.think=!0:n.think=r.think:n.think=!1,r.deterministic&&(n.options={repeat_last_n:0,top_p:1,top_k:1,temperature:0,repeat_penalty:1,seed:123}),n},parseResponse:e=>{if(e.error)throw new Error(S(e.error));if(typeof e.response=="string")return k(e.response);if(e.message?.content)return k(e.message.content);throw new Error(JSON.stringify(e))},buildUrl:e=>h(e,"api/"+r.apitype)},gemini:{buildPayload:(e,t)=>{let n={contents:[{parts:[{text:t}]}]},o={};return r.deterministic&&Object.assign(o,{temperature:0,topP:1,topK:1}),r.think!==""&&(X()?o.thinkingConfig={thinkingBudget:0}:o.thinkingConfig={thinkingBudget:8192}),Object.keys(o).length>0&&(n.generationConfig=o),n},parseResponse:e=>{if(e.candidates&&e.candidates[0]?.content?.parts){let n=e.candidates[0].content.parts.filter(o=>!o.thought&&o.text).map(o=>o.text);if(n.length>0)return k(n.join(`
`))}throw e.error?new Error(S(e.error)):(console.log(JSON.stringify(e)),new Error("Invalid response format"))},buildUrl:(e,t,n)=>h(e,`v1beta/models/${t}:generateContent?key=${n}`),requiresUrlApiKey:!0}},q={anthropic:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"anthropic",authStyle:"anthropic",apiStyle:"anthropic"},claude:{defaultModel:"claude-sonnet-5-5",defaultBaseUrl:"https://api.anthropic.com",authKey:"ANTHROPIC_API_KEY",keyName:"claude",authStyle:"anthropic",apiStyle:"anthropic"},openai:{defaultModel:"gpt-4o-mini",defaultBaseUrl:"https://api.openai.com",authKey:"OPENAI_API_KEY",authStyle:"bearer",apiStyle:"openai"},ollama:{defaultModel:"qwen2.5-coder:latest",defaultBaseUrl:"http://localhost:11434",authStyle:"none",apiStyle:"ollama"},ollamacloud:{defaultModel:"gpt-oss:120b",defaultBaseUrl:"https://ollama.com",authKey:"OLLAMA_API_KEY",keyName:"ollamacloud",authStyle:"bearer",apiStyle:"ollama"},zen:{defaultModel:"big-pickle",defaultBaseUrl:"https://opencode.ai/zen",authKey:"OPENCODE_API_KEY",keyName:"zen",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["big-pickle","glm-5","kimi-k2.5"]},gemini:{defaultModel:"gemini-2.5-flash",defaultBaseUrl:"https://generativelanguage.googleapis.com",authKey:"GEMINI_API_KEY",authStyle:"none",apiStyle:"gemini",hardcodedModels:["gemini-2.0-flash","gemini-2.0-flash-lite","gemini-2.5-pro","gemini-2.5-flash","gemini-2.5-flash-lite"]},mistral:{defaultModel:"codestral-latest",defaultBaseUrl:"https://api.mistral.ai",authKey:"MISTRAL_API_KEY",authStyle:"bearer",apiStyle:"openai"},xai:{defaultModel:"grok-beta",defaultBaseUrl:"https://api.x.ai",authKey:"XAI_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["grok-2","grok-beta"]},lmstudio:{defaultModel:"local-model",defaultBaseUrl:"http://127.0.0.1:1234",authStyle:"none",apiStyle:"openai"},deepseek:{defaultModel:"deepseek-coder",defaultBaseUrl:"https://api.deepseek.com",authKey:"DEEPSEEK_API_KEY",authStyle:"bearer",apiStyle:"openai",hardcodedModels:["deepseek-coder","deepseek-chat"]},openrouter:{defaultModel:"openai/gpt-4o-mini",defaultBaseUrl:"https://openrouter.ai/api",authKey:"OPENROUTER_API_KEY",authStyle:"bearer",apiStyle:"openai"},groq:{defaultModel:"llama-3.3-70b-versatile",defaultBaseUrl:"https://api.groq.com/openai",authKey:"GROQ_API_KEY",authStyle:"bearer",apiStyle:"openai"}};function D(e){return q[e]}function Q(){return Object.keys(q)}function be(e){let t=D(e);if(!t)throw new Error(`Unknown provider: ${e}`);if(e==="mistral")return $e(t);switch(t.apiStyle){case"openai":case"anthropic":return Le(t);case"ollama":return Ge(t);case"gemini":return"";default:return""}}function X(){return r.think==="false"||r.think==="0"}function Y(){return r.think!==""&&!X()}function We(e,t,n){let o=e;return r.think!==""&&n==="openai"&&(X()?(o+=' Answers directly and concisely, without showing any thinking steps or internal reasoning. Never include phrases like "Let me think".',o+=" /no_think"):o="Think step by step and explain the reasoning process, When answering, first output your reasoning inside <think> and </think> tags, then give the final answer."+o),t?o:r.prompt+Ye()+o}function Ye(){return`
.Translate the code into `+r.language+` programming language
`}function ze(e,t,n,o){let i=r.model||e.defaultModel,a=We(n,o,e.apiStyle),s=me(e);if(t.requiresUrlApiKey&&s?.[1]&&e.authKey)return`Cannot read ~/.r2ai.${z(e.authKey)}-key`;let l=t.buildPayload(i,a),u=ye(e,s?.[0]||null);e.apiStyle==="ollama"&&u.push("Accept: application/x-ndjson");let v=ge(u),c=t.buildUrl(he(e),i,s?.[0]||void 0);try{return t.parseResponse(fe(c,v,JSON.stringify(l)))}catch(d){return"ERROR: "+d.message}}function ke(e,t){let n=D(r.api);return n?ze(n,Je[n.apiStyle],e,t):`Unknown value for 'decai -e api'. Available: ${Q().join(", ")}`}function Ve(){return Object.fromEntries(Object.entries(q).filter(([,e])=>e.authKey).map(([e,t])=>[e,t.authKey]))}function K(e,t){let n=r2.cmd("'%"+t).trim();if(!n.includes("=")&&n!=="")return[n.trim(),null,"env"];let o=e.toLowerCase(),i=N;if(_(i)){let s=r2.cmd("'cat "+i),l=le(s);if(o in l)return[l[o],null,"txt"]}let a="~/.r2ai."+o+"-key";if(_(a)){let s=r2.cmd("'cat "+a);return s===""?[null,"Cannot read "+a,"no"]:[s.trim(),null,"file"]}return[null,"Not available","nope"]}function xe(){H(w,N),r2.cmd("'ed "+N)}function Z(){Object.entries(Ve()).forEach(([e,t])=>{let n=K(e,t)[2];console.log(n," ",e)})}function ve(e){let t=D(e);if(!t){console.error(`Unknown provider: ${e}`);return}try{let n=be(e);n&&console.log(n),t.hardcodedModels&&t.hardcodedModels.forEach(o=>console.log(o)),e==="mistral"&&console.log("ministral-8b-latest"),!n&&!t.hardcodedModels&&console.log(t.defaultModel)}catch(n){let o=n;console.error(`Error listing models for ${e}:`,o.message),console.log(t.defaultModel)}}var ee=!1;function Qe(e){return e==="true"||e==="1"}function P(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=Qe(n)}}}function R(e){let t=r;return{get:()=>t[e],set:n=>{t[e]=n}}}function Xe(e){let t=e.trim();return t==="chat"?"chat":t==="generate"?"generate":null}var Ee={pipeline:{get:()=>r.pipeline,set:e=>{r.pipeline=e;try{r.decopipe=JSON.parse(r2.cmd("cat "+e))}catch(t){console.error(t)}}},model:{get:()=>r.model,set:e=>{e==="?"?ve(r.api):r.model=e.trim()}},deterministic:P("deterministic"),files:P("useFiles"),think:{get:()=>r.think||"false",set:e=>{r.think=e}},apitype:{get:()=>r.apitype,set:e=>{let t=Xe(e);t?r.apitype=t:console.error("Invalid apitype. Use chat or generate.")}},debug:P("debug"),http:{get:()=>r.http,set:e=>{let t=e.trim();t==="auto"||t==="r2"||t==="curl"?r.http=t:console.error("Invalid http backend. Use auto, r2 or curl.")}},timeout:{get:()=>r.timeout,set:e=>{r.timeout=Math.max(0,parseInt(e,10)||0)}},api:{get:()=>r.api,set:e=>{e==="?"?console.error(Q().join(`
`)):r.api=e}},lang:R("language"),hlang:R("humanLanguage"),cache:P("cache"),cmds:R("commands"),tts:P("tts"),yolo:P("yolo"),prompt:R("prompt"),ctxfile:R("contextFile"),baseurl:R("baseurl"),headers:{get:()=>ue(r.extraHeaders),set:e=>{r.extraHeaders=W(e)}},maxtokens:{get:()=>r.maxInputTokens,set:e=>{r.maxInputTokens=parseInt(e,10)||-1}}};function Ze(){let e={host:r.host,port:r.port,lastOutput:r.lastOutput};Object.assign(r,E,e),r.decopipe={...E.decopipe},r.extraHeaders=[...E.extraHeaders]}function et(e){let t=e.trim();return!t||t.startsWith("#")?null:t.startsWith("decai -e ")?t.slice(9).trim():t}function x(e){let t=e.indexOf("="),n=t===-1?e:e.slice(0,t),o=t===-1?void 0:e.slice(t+1),i=Ee[n];if(!i){console.error("Unknown config key");return}typeof o<"u"?i.set(o):console.log(i.get())}function we(){Object.entries(Ee).forEach(([e,t])=>{let n=t.get();console.log("decai -e "+e+"="+n)})}function Ae(){if(Ze(),!_(U))return;let e=r2.call("cat "+U);for(let t of e.split(/\r?\n/)){let n=et(t);n&&n[0]!="#"&&x(n)}}function Oe(){ee||(Ae(),ee=!0)}function Se(){H(w,U),r2.cmd("'ed "+U),Ae(),ee=!0}function p(e,t,n=!1){let o=(t||"").replace(/`/g,""),i=e.replace(/'/g,"");if(r.api==="r2"||r.api==="r2ai"){let s=re(".pdc.txt");se(s,o);let l=i.startsWith("-")?i:["-i",s,i].join(" "),u=r.baseurl||r.host+":"+r.port,v=h(u,"cmd/"+encodeURIComponent(l)),c=O(v,[]);return c.error?`Error: ${c.error}`:c.result||JSON.stringify(c)||"Cannot curl, use r2ai-server or r2ai -w"}if(i.startsWith("-"))return"";let a=i+`:
`+o;return r.maxInputTokens>0&&a.length>r.maxInputTokens&&(a=a.slice(0,r.maxInputTokens)),ke(a,n)}function Re(e){let t=r2.cmd("e scr.color");r2.cmd("e scr.color=0");try{return e()}finally{r2.cmd("e scr.color="+t)}}function Pe(){let e="",t=n=>e+=" "+b+" "+n+`
`;e+="Usage: "+b+` (-h) ...
`,e+="Version: "+te+`
`,t("-a [query] - solve query with auto mode"),t("-b [url] - set base URL (alias for decai -e baseurl)"),t("-d [f1 ..] - decompile given functions"),t("-dd [..] - same as above, but ignoring cache"),t("-dD [query] - decompile current function with given extra query"),t("-dr - decompile function and its called ones (recursive)"),t("-e - display and change eval config vars"),t("-E - edit and run decai.txt"),t("-h - show this help"),t("-H - help setting up r2ai"),t("-i [f] [q] - include given file and query"),t("-k - list API key status"),t("-K - edit apikeys.txt"),t("-m [model] - use -m? or -e model=? to list the available models"),t("-n - suggest better function name"),t("-p [provider] - same as decai -e api (will be provider)"),t("-q [text] - query language model with given text"),t("-Q [text] - query on top of the last output"),t("-r [prompt] - change role prompt (same as: decai -e prompt)"),t("-R - reset role prompt to default prompt"),t("-s - function signature"),t("-v - show local variables"),t("-V - find vulnerabilities"),t("-x[*] - eXplain current function (-x* for r2 script)"),r2.log(e.trim())}function j(e,t,n,r){if(n){let s=r2.cmd("anos").trim();if(s.length>0)return s}let i="";if(r){let s=r2.cmd("s");i+=`## Context functions:
`;let l=r2.cmdAt("axff~^C[2]~$$",s);for(let u of l.split(/\n/g))i+=r2.cmd("pdc@"+u);r2.cmd("s "+s)}let a=t?" "+e:"";try{let s=e.slice(2).trim(),l="";o.contextFile!==""&&r2.cmd2("test -f "+o.contextFile).value===0&&(l+=`## Context:
`,t("-a [query] - solve query with auto mode"),t("-b [url] - set base URL (alias for decai -e baseurl)"),t("-d [f1 ..] - decompile given functions"),t("-dd [..] - same as above, but ignoring cache"),t("-dD [query] - decompile current function with given extra query"),t("-dr - decompile function and its called ones (recursive)"),t("-e - display and change eval config vars"),t("-E - edit and run decai.txt"),t("-h - show this help"),t("-H - help setting up r2ai"),t("-i [f] [q] - include given file and query"),t("-k - list API key status"),t("-K - edit apikeys.txt"),t("-m [model] - use -m? or -e model=? to list the available models"),t("-n - suggest better function name"),t("-p [provider] - same as decai -e api (will be provider)"),t("-q [text] - query language model with given text"),t("-Q [text] - query on top of the last output"),t("-r [prompt] - change role prompt (same as: decai -e prompt)"),t("-R - reset role prompt to default prompt"),t("-s - function signature"),t("-v - show local variables"),t("-V - find vulnerabilities"),t("-x[*] - eXplain current function (-x* for r2 script)"),r2.log(e.trim())}function j(e,t,n,o){if(n){let s=r2.cmd("anos").trim();if(s.length>0)return s}let i="";if(o){let s=r2.cmd("s");i+=`## Context functions:
`;let l=r2.cmdAt("axff~^C[2]~$$",s);for(let u of l.split(/\n/g))i+=r2.cmd("pdc@"+u);r2.cmd("s "+s)}let a=t?" "+e:"";try{let s=e.slice(2).trim(),l="";r.contextFile!==""&&r2.cmd2("test -f "+r.contextFile).value===0&&(l+=`## Context:
[START]
`+r2.cmd("cat "+o.contextFile)+`
`+r2.cmd("cat "+r.contextFile)+`
[END]
`);let{body:u,count:v}=Pe(()=>{let d=`## Before:
`,y=0;for(let g of o.commands.split(",")){if(g.trim()==="")continue;let I=t||s.trim().length===0?g:g+"@@= "+s,R=r2.cmd(I);R.length>5&&(d+="Output of "+g+`:
`);let{body:u,count:v}=Re(()=>{let d=`## Before:
`,y=0;for(let g of r.commands.split(",")){if(g.trim()==="")continue;let C=t||s.trim().length===0?g:g+"@@= "+s,I=r2.cmd(C);I.length>5&&(d+="Output of "+g+`:
[START]
`+R+`
`+I+`
[END]
`,y++)}return d+=`## After:
`,{body:d,count:y}});if(v===0){console.error("Nothing to do.");return}let c="";if(o.decopipe.use){let d=o.model;try{let y=o.decopipe[o.decopipe.default],g=l+u;for(let I of y.pipeline){I.model&&(o.model=I.model);let R=I.query+". "+y.globalQuery;c=p(R,g,!0),o.debug&&console.log(`QUERY
`,R,`
`,{body:d,count:y}});if(v===0){console.error("Nothing to do.");return}let c="";if(r.decopipe.use){let d=r.model;try{let y=r.decopipe[r.decopipe.default],g=l+u;for(let C of y.pipeline){C.model&&(r.model=C.model);let I=C.query+". "+y.globalQuery;c=p(I,g,!0),r.debug&&console.log(`QUERY
`,I,`
INPUT
`,g,`
OUTPUT
`,c),g=c}c=g}finally{o.model=d}}else{let d=a;l+=u+i,c=p(d,l,!1),o.lastOutput=c}return n&&c.length>1&&r2.call("ano=base64:"+$(c)),c.startsWith("```")&&(c=c.replace(/```.*\n/,"").replace(/```$/,"")),c.trim()}catch(s){let l=s;console.error(l,l.stack);return}}function et(){let e=Pe(()=>"[START]"+o.commands.split(",").map(r=>r2.cmd(r)).join(`
`)+"[END]"),n=p("Analyze function calls, references, comments and strings, loops and ignore registers and memory accesses. Explain the purpose of this function in a single short sentence. /no_think Do not introduce or argue the response, translation of the explanation in "+o.humanLanguage,e,!0).trim().split(/\n/g);return n[n.length-1].trim()}function tt(){let e=o.language,t=r2.cmd("afv;pdc");o.language="C";let n="'afs "+p("analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the return. Do NOT print the function body, ONLY output the function signature, like if it was going to be used in a C header",t,!1),r=n.indexOf("{");return r!==-1&&(n=n.substring(0,r)),o.language=e,n}function nt(e,t){let n=[];for(;;){let r=L.auto;if(n.length>0){r+=`## Command Results
`,c),g=c}c=g}finally{r.model=d}}else{let d=a;l+=u+i,c=p(d,l,!1),r.lastOutput=c}return n&&c.length>1&&r2.call("ano=base64:"+$(c)),c.startsWith("```")&&(c=c.replace(/```.*\n/,"").replace(/```$/,"")),c.trim()}catch(s){let l=s;console.error(l,l.stack);return}}function tt(){let e=Re(()=>"[START]"+r.commands.split(",").map(o=>r2.cmd(o)).join(`
`)+"[END]"),n=p("Analyze function calls, references, comments and strings, loops and ignore registers and memory accesses. Explain the purpose of this function in a single short sentence. /no_think Do not introduce or argue the response, translation of the explanation in "+r.humanLanguage,e,!0).trim().split(/\n/g);return n[n.length-1].trim()}function nt(){let e=r.language,t=r2.cmd("afv;pdc");r.language="C";let n="'afs "+p("analyze the uses of the arguments and return value to infer the signature, identify which is the correct type for the return. Do NOT print the function body, ONLY output the function signature, like if it was going to be used in a C header",t,!1),o=n.indexOf("{");return o!==-1&&(n=n.substring(0,o)),r.language=e,n}function rt(e,t){let n=[];for(;;){let o=L.auto;if(n.length>0){o+=`## Command Results
`;for(let a of n){let s=JSON.parse(a);r+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}r+=`
`;for(let a of n){let s=JSON.parse(a);o+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}o+=`
## User Prompt
`+e,o.debug&&console.log(`#### input
`,r,`
#### /input`),console.log("Thinking...");let i=p("",r,!0);o.debug&&console.log(`#### output
`+e,r.debug&&console.log(`#### input
`,o,`
#### /input`),console.log("Thinking...");let i=p("",o,!0);r.debug&&console.log(`#### output
`,i,`
#### /output`);try{let a=JSON.parse(ie(re(k(i))));if(a.action==="r2cmd"||a.action==="response"||a.action===a.command){let s=a.command||"";a.reason&&(console.log("[r2cmd] Reasoning: "+a.reason),o.tts&&(r2.syscmd("pkill say"),r2.syscmd("say -v Alex -r 250 '"+a.reason.replace(/'/g,"")+"' &"))),console.log("[r2cmd] Action: "+a.description),console.log("[r2cmd] Command: "+s);let l=s;o.yolo||(l=ot(s,t)),console.log("[r2cmd] Running: "+l);let u=r2.cmd2(l),v=u.logs?u.logs.map(y=>y.type+": "+y.message).join(`
`):"",c=(u.res+v).trim();console.log(c);let d=oe(c);o.debug&&console.log(`<r2output>
#### /output`);try{let a=JSON.parse(ae(ie(k(i))));if(a.action==="r2cmd"||a.action==="response"||a.action===a.command){let s=a.command||"";a.reason&&(console.log("[r2cmd] Reasoning: "+a.reason),r.tts&&(r2.syscmd("pkill say"),r2.syscmd("say -v Alex -r 250 '"+a.reason.replace(/'/g,"")+"' &"))),console.log("[r2cmd] Action: "+a.description),console.log("[r2cmd] Command: "+s);let l=s;r.yolo||(l=ot(s,t)),console.log("[r2cmd] Running: "+l);let u=r2.cmd2(l),v=u.logs?u.logs.map(y=>y.type+": "+y.message).join(`
`):"",c=(u.res+v).trim();console.log(c);let d=oe(c);r.debug&&console.log(`<r2output>
`,d,`
<(r2output>`),n.push(JSON.stringify({action:"response",command:l,description:a.description,response:d}))}else if(a.action==="reply"){console.log(`Done
`,a.response);break}else{console.log(`Unknown response
`,JSON.stringify(i));break}}catch(a){let s=i.indexOf('response": "');if(s!==-1){let l=i.slice(s+12).replace(/\\n/g,`
`).replace(/\\/g,"");console.log(l)}else console.log(i),console.error(a);break}}}function ot(e,t){for(;;){let n=r2.cmd("'?ie Tweak command? ('?' for help)").trim();if(n==="q!"){console.error("Break!");break}if(n==="?"){rt();continue}else if(n.startsWith(":")){console.log(r2.cmd(n.slice(1)));continue}else if(n.startsWith("-e")){t(n);continue}else{if(n==="!")return"?e do NOT execute '"+e+"' again, continue without it";if(n.startsWith("!")){console.log(r2.syscmd(n.slice(1)));continue}else{if(n==="q")return"?e All data collected!. Do not call more commands, reply the solutions";if(n){let r=n.indexOf("#");return r!==-1?n.slice(0,r).trim():n}else return e}}}return e}function rt(){console.log(" '!' do not run this command"),console.log(" '!c' run system command"),console.log(" 'q' to quit auto and try to solve"),console.log(" 'q!' quit auto without solving"),console.log(" 'c # C' use given command with comment"),console.log(" ':c' run r2 command without feeding auto"),console.log(" '-e' set decai configuration variables")}function it(e){return e.slice(2).trim()}function B(){return r2.cmd("afi.").trim().length>0}var F="Cannot find function at current offset";function Ce(e,t){if(e===""||!e.startsWith("-")){Se();return}let n="",r=e[1],i=it(e);switch(r){case"H":console.log(L.decai);break;case"a":nt(i,t);break;case"m":{i==="="?x("model="):i?x("model="+i):x("model");break}case"n":case"f":{if(!B()){n=F;break}n=r2.cmd("axff~$[3]");let a=r2.cmd("fd.").trim().split(/\n/).filter(s=>!s.startsWith("secti")).join(",");n=p("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: "+a,n,!1).trim(),n+=" @ "+r2.cmd("?v $FB").trim();break}case"v":{if(!B()){n=F;break}n=r2.cmd("afv;pdc"),n=p("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,!1);break}case"i":{let a=i.split(/\s+/).filter(Boolean);if(a.length>=2){let s=a[0],l=a.slice(1).join(" "),u=r2.cmd("cat "+s);console.log(p(l,u,!0))}else console.log("Use: decai -i [file] [query ...]");break}case"p":{i?x("api="+i):Z();break}case"r":{i?o.prompt=i:console.log(o.prompt);break}case"R":o.prompt=T;break;case"s":if(!B()){n=F;break}n=tt();break;case"V":n=p("find vulnerabilities, dont show the code, only show the response, provide a sample exploit",o.lastOutput,!1);break;case"K":ke();break;case"E":Oe();break;case"k":Z();break;case"b":{i?x("baseurl="+i):console.log(o.baseurl);break}case"e":{i?x(i):Ae();break}case"q":try{n=p(i,null,!0)}catch(a){let s=a;console.error(s,s.stack)}break;case"Q":n=p(i,o.lastOutput,!1);break;case"x":if(!B()){n=F;break}n=et(),(e[2]==="*"||e[2]==="r")&&(n="'CC "+n);break;case"d":e[2]==="r"?n=j(e.slice(2),!0,o.cache,!0)||"":e[2]==="d"?n=j(e,!1,!1,!1)||"":e[2]==="D"?n=j(e,!0,!1,!1)||"":n=j(e,!1,o.cache,!1)||"";break;default:Se();break}return n||void 0}function Ie(e){Ee();let t=Ce(e,Ie);return t&&r2.log(t),!0}function at(){r2.unload("core",b),r2.plugin("core",function(){function e(t){if(t.startsWith(b)){let n=t.slice(b.length).trim();return Ie(n)}return!1}return{name:b,license:"MIT",desc:"r2 decompiler based on r2ai",call:e}})}at();
`).replace(/\\/g,"");console.log(l)}else console.log(i),console.error(a);break}}}function ot(e,t){for(;;){let n=r2.cmd("'?ie Tweak command? ('?' for help)").trim();if(n==="q!"){console.error("Break!");break}if(n==="?"){it();continue}else if(n.startsWith(":")){console.log(r2.cmd(n.slice(1)));continue}else if(n.startsWith("-e")){t(n);continue}else{if(n==="!")return"?e do NOT execute '"+e+"' again, continue without it";if(n.startsWith("!")){console.log(r2.syscmd(n.slice(1)));continue}else{if(n==="q")return"?e All data collected!. Do not call more commands, reply the solutions";if(n){let o=n.indexOf("#");return o!==-1?n.slice(0,o).trim():n}else return e}}}return e}function it(){console.log(" '!' do not run this command"),console.log(" '!c' run system command"),console.log(" 'q' to quit auto and try to solve"),console.log(" 'q!' quit auto without solving"),console.log(" 'c # C' use given command with comment"),console.log(" ':c' run r2 command without feeding auto"),console.log(" '-e' set decai configuration variables")}function at(e){return e.slice(2).trim()}function B(){return r2.cmd("afi.").trim().length>0}var F="Cannot find function at current offset";function Ce(e,t){if(e===""||!e.startsWith("-")){Pe();return}let n="",o=e[1],i=at(e);switch(o){case"H":console.log(L.decai);break;case"a":rt(i,t);break;case"m":{i==="="?x("model="):i?x("model="+i):x("model");break}case"n":case"f":{if(!B()){n=F;break}n=r2.cmd("axff~$[3]");let a=r2.cmd("fd.").trim().split(/\n/).filter(s=>!s.startsWith("secti")).join(",");n=p("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: "+a,n,!1).trim(),n+=" @ "+r2.cmd("?v $FB").trim();break}case"v":{if(!B()){n=F;break}n=r2.cmd("afv;pdc"),n=p("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,!1);break}case"i":{let a=i.split(/\s+/).filter(Boolean);if(a.length>=2){let s=a[0],l=a.slice(1).join(" "),u=r2.cmd("cat "+s);console.log(p(l,u,!0))}else console.log("Use: decai -i [file] [query ...]");break}case"p":{i?x("api="+i):Z();break}case"r":{i?r.prompt=i:console.log(r.prompt);break}case"R":r.prompt=T;break;case"s":if(!B()){n=F;break}n=nt();break;case"V":n=p("find vulnerabilities, dont show the code, only show the response, provide a sample exploit",r.lastOutput,!1);break;case"K":xe();break;case"E":Se();break;case"k":Z();break;case"b":{i?x("baseurl="+i):console.log(r.baseurl);break}case"e":{i?x(i):we();break}case"q":try{n=p(i,null,!0)}catch(a){let s=a;console.error(s,s.stack)}break;case"Q":n=p(i,r.lastOutput,!1);break;case"x":if(!B()){n=F;break}n=tt(),(e[2]==="*"||e[2]==="r")&&(n="'CC "+n);break;case"d":e[2]==="r"?n=j(e.slice(2),!0,r.cache,!0)||"":e[2]==="d"?n=j(e,!1,!1,!1)||"":e[2]==="D"?n=j(e,!0,!1,!1)||"":n=j(e,!1,r.cache,!1)||"";break;default:Pe();break}return n||void 0}function Ie(e){Oe();let t=Ce(e,Ie);return t&&r2.log(t),!0}function st(){r2.unload("core",b),r2.plugin("core",function(){function e(t){if(t.startsWith(b)){let n=t.slice(b.length).trim();return Ie(n)}return!1}return{name:b,license:"MIT",desc:"r2 decompiler based on r2ai",call:e}})}st();
+2 -1
View File
@@ -1,10 +1,11 @@
PLUGIN_NAME=decai
R2F_C_FLAGS=-c -S
R2FRIDA_COMPILE?=r2pm -r r2frida-compile
all: build
build:
r2pm -r r2frida-compile $(R2F_C_FLAGS) -o ../$(PLUGIN_NAME).r2.js main.ts
$(R2FRIDA_COMPILE) $(R2F_C_FLAGS) -o ../$(PLUGIN_NAME).r2.js main.ts
test: build
r2 -i ../$(PLUGIN_NAME).r2.js -q -c 'af;decai -d' /bin/ls
+11
View File
@@ -83,6 +83,17 @@ export const configHandlers: ConfigHandlers = {
},
},
debug: boolHandler("debug"),
http: {
get: () => state.http,
set: (v: string) => {
const backend = v.trim();
if (backend === "auto" || backend === "r2" || backend === "curl") {
state.http = backend;
} else {
console.error("Invalid http backend. Use auto, r2 or curl.");
}
},
},
timeout: {
get: () => state.timeout,
set: (v: string) => { state.timeout = Math.max(0, parseInt(v, 10) || 0); },
+2 -1
View File
@@ -1,4 +1,4 @@
export const VERSION = "1.5.0";
export const VERSION = "1.5.4";
export const COMMAND = "decai";
export const DECAI_CONFIG_DIR = "~/.config/r2ai";
export const API_KEYS_PATH = DECAI_CONFIG_DIR + "/apikeys.txt";
@@ -33,6 +33,7 @@ These are the most recommended models for decompiling in local:
* 'decai -e apitype=chat|generate' choose the Ollama API endpoint (default: chat)
* 'decai -e headers=Authorization: Bearer ...\\nUser-Agent: curl/8.7.1' add or override HTTP headers
* "export DECAI_HEADERS='Authorization: Bearer ...\\nUser-Agent: curl/8.7.1'" set extra headers from the environment
* 'decai -e http=auto|r2|curl' select the HTTP transport (default: auto, prefers r2.http)
* 'decai -e timeout=0' disable curl timeouts, or set a positive number of seconds
* 'decai -e deterministic=true' to remove randomness from decompilation responses
+14 -6
View File
@@ -14,16 +14,24 @@ function shellEscape(arg: string): string {
return `'${arg.replace(/'/g, `'"'"'`)}'`;
}
function executeCurl(options: HttpRequestOptions): string {
function executeRequest(options: HttpRequestOptions): string {
const { method, url, headers, payload } = options;
const cmdParts = ["curl", "-s"];
if (state.timeout > 0) {
cmdParts.push("--max-time", String(state.timeout));
}
const requestHeaders = mergeHeaders(
["Content-Type: application/json"],
headers,
);
if (state.http !== "curl") {
if (r2.http) {
return r2.http(method, url, requestHeaders.join("\n"), payload ?? "").body;
}
if (state.http === "r2") {
throw new Error("r2.http is not available in this runtime");
}
}
const cmdParts = ["curl", "-s"];
if (state.timeout > 0) {
cmdParts.push("--max-time", String(state.timeout));
}
requestHeaders.forEach((h) => cmdParts.push("-H", shellEscape(h)));
if (method === "POST") {
@@ -70,7 +78,7 @@ function parseNdjson(output: string): HttpResponse | null {
export function httpRequest(options: HttpRequestOptions): HttpResponse {
try {
const output = executeCurl(options).trim();
const output = executeRequest(options).trim();
if (output === "") {
return { error: "empty response" };
}
+2 -2
View File
@@ -322,7 +322,7 @@ const providerRuntimes: Record<ApiStyle, ProviderRuntime> = {
export const providerRegistry: ProviderRegistry = {
anthropic: {
defaultModel: "claude-3-7-sonnet-20250219",
defaultModel: "claude-sonnet-5-5",
defaultBaseUrl: "https://api.anthropic.com",
authKey: "ANTHROPIC_API_KEY",
keyName: "anthropic",
@@ -330,7 +330,7 @@ export const providerRegistry: ProviderRegistry = {
apiStyle: "anthropic",
},
claude: {
defaultModel: "claude-3-7-sonnet-20250219",
defaultModel: "claude-sonnet-5-5",
defaultBaseUrl: "https://api.anthropic.com",
authKey: "ANTHROPIC_API_KEY",
keyName: "claude",
+16
View File
@@ -1,5 +1,17 @@
export type R2Address = number | string | unknown;
export interface R2HttpResponse {
status: number;
body: string;
}
export type R2HttpRequest = (
method: "GET" | "POST",
url: string,
headers: string,
data?: string,
) => R2HttpResponse;
export interface R2LogEntry {
type: string;
message: string;
@@ -23,8 +35,10 @@ export type R2PluginFactory = () => R2Plugin;
export class R2PipeAdapter {
r2p: R2Pipe;
http?: R2HttpRequest;
constructor(r2p: R2Pipe) {
this.r2p = r2p;
this.http = r2p.http?.bind(r2p);
}
/**
* Run a command in the associated instance of radare2 and return the output as a string
@@ -140,6 +154,7 @@ export interface R2Pipe {
syscmd(cmd: string): string;
syscmds(cmd: string): string;
fdump(data: string): string;
http?: R2HttpRequest;
/**
* Same as cmdj but using .call which avoids command injection problems
@@ -231,6 +246,7 @@ export interface R2PipeSync {
syscmd(cmd: string): string;
syscmds(cmd: string): string;
fdump(data: string): string;
http?: R2HttpRequest;
/**
* Same as cmdj but using .call which avoids command injection problems
+1
View File
@@ -17,6 +17,7 @@ export const defaultState: AppState = {
deterministic: true,
debug: false,
timeout: 180,
http: "auto",
think: "",
apitype: "chat",
useFiles: false,
+1
View File
@@ -51,6 +51,7 @@ export interface AppState {
deterministic: boolean;
debug: boolean;
timeout: number;
http: "auto" | "r2" | "curl";
think: string;
apitype: OllamaApiType;
useFiles: boolean;
+11 -2
View File
@@ -5,9 +5,18 @@ export function tmpdir(path: string): string {
return dir + "/" + path;
}
// r2's mkdir and touch commands do not expand '~'
export function expandHome(path: string): string {
if (!path.startsWith("~/")) {
return path;
}
const home = r2.cmd("'%HOME").trim() || r2.cmd("'%USERPROFILE").trim();
return home ? home + path.slice(1) : path;
}
export function ensurePath(dir: string, file: string): void {
r2.cmd("'mkdir -p " + dir);
r2.cmd("'touch " + file);
r2.cmd("'mkdir -p " + expandHome(dir));
r2.cmd("'touch " + expandHome(file));
}
export function fileExists(path: string): boolean {
+22
View File
@@ -16,3 +16,25 @@ For meson builds, place this repository at `libr/xps/p/r2ai` and make
```meson
subdir('r2ai/r2plugin')
```
## WASI HTTP build
Use a radare2 checkout with the host HTTP bridge and place this repository at
`libr/xps/p/r2ai`. From that radare2 checkout:
```sh
echo 'EXTERNAL_PLUGINS+=r2ai' >> libr/xps/config.mk
make -C libr/xps
WASI_HTTP=1 sys/wasi-api.sh
```
This links the C plugin into the reactor; it does not load a native shared
library at runtime. Enable HTTP in the host adapter. The `auto` backend selects
`r_socket_http_get/post`, preserving provider authentication headers and JSON
bodies, subject to the radare2 sandbox. `--disable-network` also denies these calls.
WASI excludes POSIX signal/alarm handling; the host controls request timeouts,
not `r2ai.http.timeout`. Normal queries and model listing use buffered HTTP.
Vertex's automatic `gcloud` authentication and background tasks requiring
threads are unavailable. Explicit shell backends (`system`/`curl`) cannot work;
use `auto` or `socket`. Browser CORS and restricted-header rules still apply.
+36 -2
View File
@@ -8,6 +8,7 @@ R2BINDIR=$(shell r2pm -H R2PM_BINDIR)
R2INCDIR=$(shell r2 -NNH R2_INCDIR)/libr
R2LIBDIR=$(shell r2 -NNH R2_LIBDIR)
R2MANDIR=$(shell r2pm -H R2PM_MANDIR)
R2AI_PROMPTSDIR=$(HOME)/.config/r2ai/prompts
ifeq ($(R2MANDIR),)
R2MANDIR=$(shell r2 -NNH R2_PREFIX)/share/man
endif
@@ -70,6 +71,37 @@ all: $(OBJS) r2check
$(AR) rcs libr2ai.a $(OBJS)
$(CC) -pie -o r2ai$(DOTEXE) $(OBJS) $(CFLAGS) $(LDFLAGS) -lm main.c
# Binary package for r2pm -bi: plugins/, bin/, man/ and prompts/ for this r2 version and platform
# r2pm -H R2PM_* needs a recent r2pm; fall back to uname when missing
R2PM_OS_SH=$(shell r2pm -H R2PM_OS 2>/dev/null)
R2PM_ARCH_SH=$(shell r2pm -H R2PM_ARCH 2>/dev/null)
R2PM_BITS_SH=$(shell r2pm -H R2PM_BITS 2>/dev/null)
ifeq ($(R2PM_OS_SH),)
ifeq ($(shell uname -s),Darwin)
R2PM_OS_SH=darwin
else
R2PM_OS_SH=linux
endif
endif
ifeq ($(R2PM_ARCH_SH),)
ifeq ($(shell uname -m),x86_64)
R2PM_ARCH_SH=x86
else ifneq (,$(filter $(shell uname -m),aarch64 arm64))
R2PM_ARCH_SH=arm
else
R2PM_ARCH_SH=$(shell uname -m)
endif
endif
ifeq ($(R2PM_BITS_SH),)
R2PM_BITS_SH=$(shell getconf LONG_BIT 2>/dev/null || echo 64)
endif
BINDIST_ZIP=r2ai-$(shell r2 -qv)-$(R2PM_OS_SH)-$(R2PM_ARCH_SH)-$(R2PM_BITS_SH).zip
bindist: all
rm -rf build/bindist ../$(BINDIST_ZIP)
$(MAKE) user-install R2USRPLUGDIR=$(CURDIR)/build/bindist/plugins R2BINDIR=$(CURDIR)/build/bindist/bin R2MANDIR=$(CURDIR)/build/bindist/man R2AI_PROMPTSDIR=$(CURDIR)/build/bindist/prompts
cd build/bindist && zip -q -r $(CURDIR)/../$(BINDIST_ZIP) plugins bin man prompts
try: all
$(MAKE) user-install
$(MAKE) doc-install
@@ -87,12 +119,12 @@ user-install: user-uninstall
mkdir -p $(R2USRPLUGDIR)
mkdir -p $(R2BINDIR)
mkdir -p $(R2MANDIR)/man1
mkdir -p $(HOME)/.config/r2ai/prompts
mkdir -p $(R2AI_PROMPTSDIR)
$(MAKE) user-uninstall
cp -f r2ai$(DOTLIB) $(R2USRPLUGDIR)/r2ai$(DOTLIB)
cp -f r2ai$(DOTEXE) $(R2BINDIR)/r2ai$(DOTEXE)
cp -f r2ai.1 $(R2MANDIR)/man1/r2ai.1
cp -r ../prompts/* $(HOME)/.config/r2ai/prompts/
cp -r ../prompts/* $(R2AI_PROMPTSDIR)/
user-uninstall:
rm -f $(R2USRPLUGDIR)/r2ai$(DOTLIB)
@@ -120,3 +152,5 @@ r2check:
@r2 -NN -qcq --
clean:
rm -f *.o *.d
.PHONY: all bindist try doc-install fmt user-install user-uninstall install uninstall r2check clean test
+1 -1
View File
@@ -121,7 +121,7 @@ These can be set with `r2ai -e <keyname>=<value>`
```
$ r2 ./substitute
[0x000010a0]> r2ai -e api=anthropic
[0x000010a0]> r2ai -e model=claude-3-7-sonnet-20250219
[0x000010a0]> r2ai -e model=claude-sonnet-5-5
[0x000010a0]> aa
```
+1 -1
View File
@@ -15,7 +15,7 @@ R_IPI char *r2ai_anthropic_request(const R2AIArgs *args, bool vertex) {
pj_ks (pj, "anthropic_version", "vertex-2023-10-16");
pj_kb (pj, "stream", false);
} else {
pj_ks (pj, "model", R_STR_ISNOTEMPTY (args->model)? args->model: "claude-3-7-sonnet-20250219");
pj_ks (pj, "model", R_STR_ISNOTEMPTY (args->model)? args->model: "claude-sonnet-5-5");
}
pj_kn (pj, "max_tokens", args->max_tokens? args->max_tokens: 4096);
if (args->thinking_tokens >= 1024) {
+13 -2
View File
@@ -96,6 +96,8 @@ static void task_free(R2AITask *t) {
r2ai_msgs_free (t->messages);
}
r_list_free (t->tools);
r_config_free (t->llm_core->config);
free (t->llm_core);
free (t->title);
free (t->query);
free (t->system_prompt);
@@ -143,7 +145,9 @@ static RThreadFunctionRet finish(R2AITask *t, R2AITaskState st, char *err) {
return R_TH_STOP;
}
// run one llm turn, r2ai_llmcall and the providers still read core->config
/* Providers read core->config throughout a request. The HTTP server swaps
* that pointer between its web and shell contexts, so use the task snapshot.
* Tool execution still uses the original session on the main thread. */
static R2AI_ChatResponse *run_llm_once(R2AITask *t) {
char *error = NULL;
R2AIArgs args = {
@@ -154,7 +158,9 @@ static R2AI_ChatResponse *run_llm_once(R2AITask *t) {
.tools = t->tools,
.error = &error,
};
R2AI_ChatResponse *res = r2ai_llmcall (t->cps, args);
RCorePluginSession worker_session = *t->cps;
worker_session.core = t->llm_core;
R2AI_ChatResponse *res = r2ai_llmcall (&worker_session, args);
if (error) {
task_lock (t);
free (t->error);
@@ -323,6 +329,11 @@ static R2AITask *task_new(RCorePluginSession *cps, R2AITaskKind kind, const char
RCore *core = cps->core;
R2AITask *t = R_NEW0 (R2AITask);
t->cps = cps;
/* Snapshot on the main thread, before it can mutate the shared core.
* Only config is owned; providers borrow the remaining session fields. */
t->llm_core = R_NEW (RCore);
*t->llm_core = *core;
t->llm_core->config = r_config_clone (core->config);
t->kind = kind;
t->state = R2AI_TASK_PENDING;
t->title = strdup (title? title: "");
+6 -4
View File
@@ -29,7 +29,7 @@ static R2AI_HttpConfig get_http_config(RCore *core) {
return config;
}
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
// Signal handler for timeout (SIGALRM)
static void r2ai_http_sigint_handler(int sig) {
if (sig == SIGALRM) {
@@ -66,7 +66,7 @@ static HttpResponse r2ai_http_request_with_retry(HttpRequestFunc func, const HTT
// Reset interrupt flag
r2ai_http_interrupted = 0;
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
// Ignore SIGWINCH to prevent window resize from interrupting child processes
struct sigaction old_winch_act;
sigaction (SIGWINCH, &(struct sigaction){ .sa_handler = SIG_IGN }, &old_winch_act);
@@ -129,7 +129,7 @@ static HttpResponse r2ai_http_request_with_retry(HttpRequestFunc func, const HTT
r_cons_break_pop (core->cons);
}
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
// Restore SIGWINCH handler
sigaction (SIGWINCH, &old_winch_act, NULL);
#endif
@@ -145,7 +145,9 @@ static HttpResponse r2ai_http_request_with_retry(HttpRequestFunc func, const HTT
static HttpRequestFunc select_backend(const char *backend, bool is_post) {
HttpRequestFunc func = NULL;
if (!strcmp (backend, "auto")) {
#if R2__WINDOWS__
#if __wasi__
backend = "socket";
#elif R2__WINDOWS__
// TODO: we should use pwsh or r2 by default, but for not it seems the only backend that works on Windows is r2curl+system
r_sys_setenv ("R2_CURL", "1");
backend = "system";
+4 -4
View File
@@ -6,14 +6,14 @@ static HttpResponse socket_http_post_with_interrupt(const HTTPRequest *request)
#if R2__WINDOWS__
r_sys_setenv ("R2_CURL", "1");
#endif
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (request->config.timeout); // Use configured timeout
#endif
int code = 0;
int rlen = 0;
char *result = r_socket_http_post (request->url, request->headers, request->data, &code, &rlen);
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
alarm (0);
#endif
if (r2ai_http_interrupted) {
@@ -32,14 +32,14 @@ static HttpResponse socket_http_get_with_interrupt(const HTTPRequest *request) {
#if R2__WINDOWS__
r_sys_setenv ("R2_CURL", "1");
#endif
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
signal (SIGALRM, r2ai_http_sigint_handler);
alarm (request->config.timeout); // Use configured timeout
#endif
int code = 0;
int rlen = 0;
char *result = r_socket_http_get (request->url, request->headers, &code, &rlen);
#if R2__UNIX__
#if R2__UNIX__ && !__wasi__
alarm (0);
#endif
if (r2ai_http_interrupted) {
+1 -1
View File
@@ -1,5 +1,5 @@
project('r2ai', 'c',
version : '1.5.0',
version : '1.5.4',
default_options : ['warning_level=2', 'c_std=c11']
)
+1 -2
View File
@@ -2,7 +2,6 @@
#define R2AI_H
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <time.h>
#include <stdint.h>
@@ -29,7 +28,7 @@
#define r_core_call(x,y) r_core_cmd_call(x,y)
#endif
#define R2AI_VERSION "1.5.0"
#define R2AI_VERSION "1.5.4"
#define R2AI_DEFAULT_MODEL "gpt-oss:20b"
#define R2AI_DEFAULT_PROVIDER "ollama"
+4 -1
View File
@@ -52,6 +52,7 @@ typedef struct r2ai_task_t {
RThreadLock *lock;
RThreadSemaphore *gate;
RCorePluginSession *cps;
RCore *llm_core; /* shallow core view with an owned request config snapshot */
time_t created;
time_t started;
time_t finished;
@@ -82,7 +83,9 @@ typedef struct {
R_API void cmd_r2ai(RCorePluginSession *cps, const char *input);
static inline char *r2ai_cons_editor(RCons *cons, const char *file, const char *str) {
#if R2_ABIVERSION >= 120
#if R2_ABIVERSION >= 160
return r_cons_editor (cons, file, str, 0, NULL);
#elif R2_ABIVERSION >= 120
return r_cons_editor (cons, file, str, NULL);
#else
return r_cons_editor (cons, file, str);
Executable
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
# version.sh - print current version or bump to a new one
# usage: ./version.sh [new-version]
# Updates VERSION, decai/src/constants.ts, src/r2ai.h, and src/meson.build
VERSION_FILE="VERSION"
TS_FILE="decai/src/constants.ts"
R2AI_H="src/r2ai.h"
MESON_FILE="src/meson.build"
NEW_VERSION="${1:-}"
get_version() {
if [ -f "$VERSION_FILE" ]; then
tr -d '[:space:]' < "$VERSION_FILE"
else
cat <<EOF >&2
error: $VERSION_FILE not found
EOF
exit 1
fi
}
CURRENT=$(get_version)
if [ -z "$NEW_VERSION" ]; then
printf '%s\n' "$CURRENT"
exit 0
fi
# basic semver-ish validation: digits.digits.digits with optional pre-release/build
if ! printf '%s' "$NEW_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-+.]?[A-Za-z0-9.-]*)?$'; then
cat <<EOF >&2
error: invalid version '$NEW_VERSION'
expected format: MAJOR.MINOR.PATCH[-prerelease]
EOF
exit 1
fi
printf '%s\n' "$NEW_VERSION" > "$VERSION_FILE"
if [ -f "$TS_FILE" ]; then
sed -i -E "s/export const VERSION = \"[^\"]+\";/export const VERSION = \"$NEW_VERSION\";/" "$TS_FILE"
fi
if [ -f "$R2AI_H" ]; then
sed -i -E "s/#define R2AI_VERSION \"[^\"]+\"/#define R2AI_VERSION \"$NEW_VERSION\"/" "$R2AI_H"
fi
if [ -f "$MESON_FILE" ]; then
sed -i -E "s/version : '[^']+'/version : '$NEW_VERSION'/" "$MESON_FILE"
fi
printf '%s -> %s\n' "$CURRENT" "$NEW_VERSION"