Compare commits

..
Author SHA1 Message Date
pancake 9edd45188e Exclude POSIX HTTP timers from WASI builds 2026-10-18 15:04:09 +02:00
pancake 66919dc065 Use radare2 HTTP APIs for r2ai and decai in WASI 2026-10-18 10:40:41 +02:00
23 changed files with 74 additions and 140 deletions

No files matched your search

-9
View File
@@ -1,8 +1,5 @@
name: CI and Release
env:
R2V: 6.2.6
on:
push:
branches:
@@ -37,8 +34,6 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Build with Make
if: matrix.build_system == 'make'
@@ -74,8 +69,6 @@ jobs:
- name: Install radare2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Package r2pm binary
run: make -C src bindist
@@ -102,8 +95,6 @@ jobs:
- name: Install radare2
id: r2
uses: radareorg/github-actions@v3
with:
version: ${{ env.R2V }}
- name: Build with Meson
shell: powershell
+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-sonnet-5-5"
CLAUDE_MODEL="claude-3-5-sonnet-20241022"
if [ -f ~/.r2ai.anthropic-key ]; then
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
fi
DEEPSEEK_KEY=""
DEEPSEEK_MODEL="claude-sonnet-5-5"
DEEPSEEK_MODEL="claude-3-5-sonnet-20241022"
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-sonnet-5-5
CLAUDE_MODEL=claude-3-5-sonnet-20241022
EOF
exit 0
}
+1 -1
View File
@@ -49,7 +49,7 @@ python3 main.py
**Example selecting a remote models:**
```
[r2ai:0x00006aa0]> -m anthropic:claude-sonnet-5-5
[r2ai:0x00006aa0]> -m anthropic:claude-3-7-sonnet-20250219
[r2ai:0x00006aa0]> -m openai:gpt-4
```
+7 -4
View File
@@ -62,7 +62,7 @@ Uncensored:
Remote:
-m openapi:http://localhost:5001
-m openai:gpt-4
-m anthropic:claude-sonnet-5-5
-m anthropic:claude-3-7-sonnet-20250219
-m bedrock:anthropic.claude-3-5-sonnet-20241022-v1
-m kobaldcpp:http://localhost:5001
"""
@@ -80,9 +80,12 @@ OpenAI:
-m openai:gpt-4
-m openai:gpt-4-1106-preview
Anthropic:
-m anthropic:claude-opus-4-8
-m anthropic:claude-sonnet-5-5
-m anthropic:claude-haiku-4-5-20251001
-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
groq:
-m groq:gemma-7b-it
-m groq:llama2-70b-4096
+2 -1
View File
@@ -170,7 +170,8 @@ def slurp_until(endword):
def list_models():
print("ollama/llama3:latest")
print("ollama/granite-code:8b")
print("anthropic/claude-sonnet-5-5")
print("anthropic/claude-3-5-sonnet-20240620")
print("anthropic/claude-3-7-sonnet-20250219")
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-sonnet-5-5
r2ai -e model=claude-3-7-sonnet-20250219
r2ai -e max_tokens=64000
```
+1 -1
View File
@@ -1 +1 @@
1.5.4
1.5.3
+2 -8
View File
@@ -103,12 +103,6 @@ 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=?`:
```
@@ -221,11 +215,11 @@ void daemonize() {
## WASM HTTP
With radare2's WASI HTTP build, decai defaults to `r2.http()` to reach the existing
With radare2's WASI HTTP build, decai uses `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.
fallback. Older/native runtimes retain their existing curl path.
Enable HTTP in the embedding application (`enabled: true` in JavaScript or
`network=True` in Python). The radare2 sandbox must permit both `socket` and
+37 -38
View File
@@ -1,7 +1,7 @@
📦
27680 /main.js
27221 /main.js
✄
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
var ie="1.5.2",w="decai",P="~/.config/r2ai",H=P+"/apikeys.txt",T=P+"/decai.txt",D="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.",Y={decai:`# Using Decai
Decai is the radare2 plugin for decompiling functions with the help of language models.
@@ -26,7 +26,6 @@ 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
@@ -118,54 +117,54 @@ Use radare2 to resolve user requests.
### Special cases
* On Swift binaries run...
`};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(`
`};var S={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:D,lastOutput:""},o={...S,decopipe:{...S.decopipe},extraHeaders:[...S.extraHeaders]};function ae(e){return(r2.cmd("-e dir.tmp").trim()??".")+"/"+e}function j(e,t){r2.cmd("'mkdir -p "+e),r2.cmd("'touch "+t)}function K(e){return(r2.cmd2("test -h").logs?.[0]?.message??"").includes("-fdx")?!0:r2.cmd("'test -vf "+e).startsWith("found")}function W(e,t){return e+" ".repeat(Math.max(0,t-e.length))}function x(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}function se(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}function le(e){let t=e.match(/```json\s*([\s\S]*?)```/);return t&&t[1]?t[1].trim():e}function ce(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 z(e){return btoa(e)}function ue(e,t){let n=z(t);r2.cmd("p6ds "+n+" > "+e)}function E(e){let t=e;return t=t.replace(/<think>[\s\S]*?<\/think>/gi,""),t.split(`
`).filter(n=>!n.trim().startsWith("```")).join(`
`)}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,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:
`)}function V(e){o.debug&&console.log(e)}function de(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 Ne=["DECAI_HEADERS","R2AI_HEADERS"];function Te(e){let t=r2.cmd("'%"+e).trim();return t!==""&&!t.includes("=")?t:""}function Ke(e){return e.trim().toLowerCase()}function qe(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 He(e){return e.value===""?`${e.name}:`:`${e.name}: ${e.value}`}function pe(e,t){for(let n of e){let r=qe(n.trim());r&&t.set(Ke(r.name),He(r))}}function Q(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 pe(n,t),Array.from(t.values())}function fe(e){return e.join("\\n")}function C(...e){let t=new Map;for(let n of e)pe(n,t);return Array.from(t.values())}function De(){for(let e of Ne){let t=Te(e);if(t)return Q(t)}return[]}function me(){return C(De(),o.extraHeaders)}function q(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function je(e){let{method:t,url:n,headers:r,payload:i}=e,a=C(["Content-Type: application/json"],r),s=r2.http;if(s)return s(t,n,a.join(`
`),i??"").body;let l=["curl","-s"];if(o.timeout>0&&l.push("--max-time",String(o.timeout)),a.forEach(c=>l.push("-H",q(c))),t==="POST"){if(!i)throw new Error("Payload required for POST requests");let c=r2.fdump(i);l.push("--data-binary","@"+q(c),q(n));let k=l.join(" ")+"; rm "+q(c);return V(k),r2.syscmds(k)}else{l.push(q(n));let c=l.join(" ");return V(c),r2.syscmds(c)}}function Be(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 he(e){try{let t=je(e).trim();if(t==="")return{error:"empty response"};try{return JSON.parse(t)}catch{let n=Be(t);return n||{error:t,rawOutput:t}}}catch(t){return{error:t.message}}}function I(e,t){return he({method:"GET",url:e,headers:t})}function ge(e,t,n){return he({method:"POST",url:e,headers:t,payload:n})}function Z(e){return e.split("_")[0].toLowerCase()}function ye(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=B(r,e.authKey);if(i[0])return i;!n&&i[2]!=="nope"&&(n=i)}return n||B(Z(e.authKey),e.authKey)}function R(e){if(e)return typeof e=="string"?e:e.message||JSON.stringify(e)}function be(e){return o.baseurl||e.defaultBaseUrl}function ke(e,t=me()){return C(e,t)}function Fe(e){return e?["Authorization: Bearer "+e]:[]}function Le(e){let t=["anthropic-version: 2023-06-01"];return e?C(t,["x-api-key: "+e]):t}function xe(e,t){switch(e.authStyle||"none"){case"bearer":return Fe(t);case"anthropic":return Le(t);case"none":default:return[]}}function Ge(e,t,n){let r=I(e,t),i=R(r.error);return i?(console.error(i),"error invalid response"):r.data?.map(n).join(`
`)||""}function $e(e,t){let n=new Set;return e.filter(r=>{let i=t(r);return n.has(i)?!1:(n.add(i),!0)})}function ee(e){let t=ye(e)?.[0]||null;return{baseUrl:be(e),headers:ke(xe(e,t))}}function Je(e){let{baseUrl:t,headers:n}=ee(e),r=x(t,"v1/models");return Ge(r,n,i=>i.id)}function Ye(e){let{baseUrl:t,headers:n}=ee(e),r=I(x(t,"api/tags"),n),i=R(r.error);return i?(console.error(i),"error invalid response"):r.models?.map(a=>a.name).join(`
`)||""}function We(e){let{baseUrl:t,headers:n}=ee(e),r=I(x(t,"v1/models"),n);return r.data?$e(r.data,i=>i.name||i.id).map(i=>[W(i.name||i.id,30),W(""+(i.max_context_length||""),10),i.description||""].join(" ")).join(`
`):""}var ze={openai:{buildPayload:(e,t)=>({stream:!1,model:e,messages:[{role:"user",content:t}]}),parseResponse:e=>{if(e.error)throw new Error(R(e.error)||"Unknown error");if(e.choices&&e.choices[0]?.message?.content)return E(e.choices[0].message.content);throw new Error("Invalid response format")},buildUrl:e=>x(e,"v1/chat/completions")},anthropic:{buildPayload:(e,t)=>{let n={model:e,max_tokens:5128,messages:[{role:"user",content:t}]};return X()&&(n.thinking={type:"enabled",budget_tokens:4096},n.max_tokens=16e3),o.deterministic&&!X()&&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 E(t.join(`
`))}throw e.error?new Error(R(e.error)||"Unknown error"):new Error("Invalid response format")},buildUrl:e=>x(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}],X()?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(R(e.error));if(typeof e.response=="string")return E(e.response);if(e.message?.content)return E(e.message.content);throw new Error(JSON.stringify(e))},buildUrl:e=>x(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!==""&&(ne()?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 E(n.join(`
`))}throw e.error?new Error(R(e.error)):(console.log(JSON.stringify(e)),new Error("Invalid response format"))},buildUrl:(e,t,n)=>x(e,`v1beta/models/${t}:generateContent?key=${n}`),requiresUrlApiKey:!0}},F={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 L(e){return F[e]}function te(){return Object.keys(F)}function ve(e){let t=L(e);if(!t)throw new Error(`Unknown provider: ${e}`);if(e==="mistral")return We(t);switch(t.apiStyle){case"openai":case"anthropic":return Je(t);case"ollama":return Ye(t);case"gemini":return"";default:return""}}function ne(){return o.think==="false"||o.think==="0"}function X(){return o.think!==""&&!ne()}function Ve(e,t,n){let r=e;return o.think!==""&&n==="openai"&&(ne()?(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+Qe()+r}function Qe(){return`
.Translate the code into `+o.language+` programming language
`}function Xe(e,t,n,r){let i=o.model||e.defaultModel,a=Ve(n,r,e.apiStyle),s=ye(e);if(t.requiresUrlApiKey&&s?.[1]&&e.authKey)return`Cannot read ~/.r2ai.${Z(e.authKey)}-key`;let l=t.buildPayload(i,a),c=xe(e,s?.[0]||null);e.apiStyle==="ollama"&&c.push("Accept: application/x-ndjson");let k=ke(c),u=t.buildUrl(be(e),i,s?.[0]||void 0);try{return t.parseResponse(ge(u,k,JSON.stringify(l)))}catch(d){return"ERROR: "+d.message}}function Ae(e,t){let n=L(o.api);return n?Xe(n,ze[n.apiStyle],e,t):`Unknown value for 'decai -e api'. Available: ${te().join(", ")}`}function Ze(){return Object.fromEntries(Object.entries(F).filter(([,e])=>e.authKey).map(([e,t])=>[e,t.authKey]))}function B(e,t){let n=r2.cmd("'%"+t).trim();if(!n.includes("=")&&n!=="")return[n.trim(),null,"env"];let r=e.toLowerCase(),i=H;if(K(i)){let s=r2.cmd("'cat "+i),l=de(s);if(r in l)return[l[r],null,"txt"]}let a="~/.r2ai."+r+"-key";if(K(a)){let s=r2.cmd("'cat "+a);return s===""?[null,"Cannot read "+a,"no"]:[s.trim(),null,"file"]}return[null,"Not available","nope"]}function we(){j(P,H),r2.cmd("'ed "+H)}function oe(){Object.entries(Ze()).forEach(([e,t])=>{let n=B(e,t)[2];console.log(n," ",e)})}function Ee(e){let t=L(e);if(!t){console.error(`Unknown provider: ${e}`);return}try{let n=ve(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 re=!1;function et(e){return e==="true"||e==="1"}function U(e){let t=o;return{get:()=>t[e],set:n=>{t[e]=et(n)}}}function _(e){let t=o;return{get:()=>t[e],set:n=>{t[e]=n}}}function tt(e){let t=e.trim();return t==="chat"?"chat":t==="generate"?"generate":null}var Oe={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==="?"?Ee(o.api):o.model=e.trim()}},deterministic:U("deterministic"),files:U("useFiles"),think:{get:()=>o.think||"false",set:e=>{o.think=e}},apitype:{get:()=>o.apitype,set:e=>{let t=tt(e);t?o.apitype=t:console.error("Invalid apitype. Use chat or generate.")}},debug:U("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(te().join(`
`)):o.api=e}},lang:_("language"),hlang:_("humanLanguage"),cache:U("cache"),cmds:_("commands"),tts:U("tts"),yolo:U("yolo"),prompt:_("prompt"),ctxfile:_("contextFile"),baseurl:_("baseurl"),headers:{get:()=>fe(o.extraHeaders),set:e=>{o.extraHeaders=Q(e)}},maxtokens:{get:()=>o.maxInputTokens,set:e=>{o.maxInputTokens=parseInt(e,10)||-1}}};function nt(){let e={host:o.host,port:o.port,lastOutput:o.lastOutput};Object.assign(o,S,e),o.decopipe={...S.decopipe},o.extraHeaders=[...S.extraHeaders]}function ot(e){let t=e.trim();return!t||t.startsWith("#")?null:t.startsWith("decai -e ")?t.slice(9).trim():t}function O(e){let t=e.indexOf("="),n=t===-1?e:e.slice(0,t),r=t===-1?void 0:e.slice(t+1),i=Oe[n];if(!i){console.error("Unknown config key");return}typeof r<"u"?i.set(r):console.log(i.get())}function Se(){Object.entries(Oe).forEach(([e,t])=>{let n=t.get();console.log("decai -e "+e+"="+n)})}function Pe(){if(nt(),!K(T))return;let e=r2.call("cat "+T);for(let t of e.split(/\r?\n/)){let n=ot(t);n&&n[0]!="#"&&O(n)}}function Ce(){re||(Pe(),re=!0)}function Ie(){j(P,T),r2.cmd("'ed "+T),Pe(),re=!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=ae(".pdc.txt");ue(s,r);let l=i.startsWith("-")?i:["-i",s,i].join(" "),c=o.baseurl||o.host+":"+o.port,k=x(c,"cmd/"+encodeURIComponent(l)),u=I(k,[]);return u.error?`Error: ${u.error}`:u.result||JSON.stringify(u)||"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)),Ae(a,n)}function Ue(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 Re(){let e="",t=n=>e+=" "+w+" "+n+`
`;e+="Usage: "+w+` (-h) ...
`,e+="Version: "+ie+`
`,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 G(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 c of l.split(/\n/g))i+=r2.cmd("pdc@"+c);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:
[START]
`+r2.cmd("cat "+r.contextFile)+`
`+r2.cmd("cat "+o.contextFile)+`
[END]
`);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+`:
`);let{body:c,count:k}=Ue(()=>{let d=`## Before:
`,A=0;for(let v of o.commands.split(",")){if(v.trim()==="")continue;let M=t||s.trim().length===0?v:v+"@@= "+s,N=r2.cmd(M);N.length>5&&(d+="Output of "+v+`:
[START]
`+I+`
`+N+`
[END]
`,y++)}return d+=`## After:
`,{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,`
`,A++)}return d+=`## After:
`,{body:d,count:A}});if(k===0){console.error("Nothing to do.");return}let u="";if(o.decopipe.use){let d=o.model;try{let A=o.decopipe[o.decopipe.default],v=l+c;for(let M of A.pipeline){M.model&&(o.model=M.model);let N=M.query+". "+A.globalQuery;u=p(N,v,!0),o.debug&&console.log(`QUERY
`,N,`
INPUT
`,g,`
`,v,`
OUTPUT
`,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
`,u),v=u}u=v}finally{o.model=d}}else{let d=a;l+=c+i,u=p(d,l,!1),o.lastOutput=u}return n&&u.length>1&&r2.call("ano=base64:"+z(u)),u.startsWith("```")&&(u=u.replace(/```.*\n/,"").replace(/```$/,"")),u.trim()}catch(s){let l=s;console.error(l,l.stack);return}}function rt(){let e=Ue(()=>"[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 it(){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 at(e,t){let n=[];for(;;){let r=Y.auto;if(n.length>0){r+=`## Command Results
`;for(let a of n){let s=JSON.parse(a);o+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}o+=`
`;for(let a of n){let s=JSON.parse(a);r+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}r+=`
## User Prompt
`+e,r.debug&&console.log(`#### input
`,o,`
#### /input`),console.log("Thinking...");let i=p("",o,!0);r.debug&&console.log(`#### output
`+e,o.debug&&console.log(`#### input
`,r,`
#### /input`),console.log("Thinking...");let i=p("",r,!0);o.debug&&console.log(`#### output
`,i,`
#### /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>
#### /output`);try{let a=JSON.parse(ce(le(E(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=st(s,t)),console.log("[r2cmd] Running: "+l);let c=r2.cmd2(l),k=c.logs?c.logs.map(A=>A.type+": "+A.message).join(`
`):"",u=(c.res+k).trim();console.log(u);let d=se(u);o.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==="?"){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();
`).replace(/\\/g,"");console.log(l)}else console.log(i),console.error(a);break}}}function st(e,t){for(;;){let n=r2.cmd("'?ie Tweak command? ('?' for help)").trim();if(n==="q!"){console.error("Break!");break}if(n==="?"){lt();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 lt(){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 ct(e){return e.slice(2).trim()}function $(){return r2.cmd("afi.").trim().length>0}var J="Cannot find function at current offset";function _e(e,t){if(e===""||!e.startsWith("-")){Re();return}let n="",r=e[1],i=ct(e);switch(r){case"H":console.log(Y.decai);break;case"a":at(i,t);break;case"m":{i==="="?O("model="):i?O("model="+i):O("model");break}case"n":case"f":{if(!$()){n=J;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(!$()){n=J;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(" "),c=r2.cmd("cat "+s);console.log(p(l,c,!0))}else console.log("Use: decai -i [file] [query ...]");break}case"p":{i?O("api="+i):oe();break}case"r":{i?o.prompt=i:console.log(o.prompt);break}case"R":o.prompt=D;break;case"s":if(!$()){n=J;break}n=it();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":we();break;case"E":Ie();break;case"k":oe();break;case"b":{i?O("baseurl="+i):console.log(o.baseurl);break}case"e":{i?O(i):Se();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(!$()){n=J;break}n=rt(),(e[2]==="*"||e[2]==="r")&&(n="'CC "+n);break;case"d":e[2]==="r"?n=G(e.slice(2),!0,o.cache,!0)||"":e[2]==="d"?n=G(e,!1,!1,!1)||"":e[2]==="D"?n=G(e,!0,!1,!1)||"":n=G(e,!1,o.cache,!1)||"";break;default:Re();break}return n||void 0}function Me(e){Ce();let t=_e(e,Me);return t&&r2.log(t),!0}function ut(){r2.unload("core",w),r2.plugin("core",function(){function e(t){if(t.startsWith(w)){let n=t.slice(w.length).trim();return Me(n)}return!1}return{name:w,license:"MIT",desc:"r2 decompiler based on r2ai",call:e}})}ut();
-11
View File
@@ -83,17 +83,6 @@ 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); },
+1 -2
View File
@@ -1,4 +1,4 @@
export const VERSION = "1.5.4";
export const VERSION = "1.5.2";
export const COMMAND = "decai";
export const DECAI_CONFIG_DIR = "~/.config/r2ai";
export const API_KEYS_PATH = DECAI_CONFIG_DIR + "/apikeys.txt";
@@ -33,7 +33,6 @@ 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
+8 -9
View File
@@ -14,19 +14,18 @@ function shellEscape(arg: string): string {
return `'${arg.replace(/'/g, `'"'"'`)}'`;
}
function executeRequest(options: HttpRequestOptions): string {
function executeCurl(options: HttpRequestOptions): string {
const { method, url, headers, payload } = options;
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");
}
// WASI provides HTTP through the host; never fall back after a denied request.
const http = (r2 as unknown as { http?: (
method: string, url: string, headers: string, data?: string,
) => { status: number; body: string } }).http;
if (http) {
return http(method, url, requestHeaders.join("\n"), payload ?? "").body;
}
const cmdParts = ["curl", "-s"];
if (state.timeout > 0) {
@@ -78,7 +77,7 @@ function parseNdjson(output: string): HttpResponse | null {
export function httpRequest(options: HttpRequestOptions): HttpResponse {
try {
const output = executeRequest(options).trim();
const output = executeCurl(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-sonnet-5-5",
defaultModel: "claude-3-7-sonnet-20250219",
defaultBaseUrl: "https://api.anthropic.com",
authKey: "ANTHROPIC_API_KEY",
keyName: "anthropic",
@@ -330,7 +330,7 @@ export const providerRegistry: ProviderRegistry = {
apiStyle: "anthropic",
},
claude: {
defaultModel: "claude-sonnet-5-5",
defaultModel: "claude-3-7-sonnet-20250219",
defaultBaseUrl: "https://api.anthropic.com",
authKey: "ANTHROPIC_API_KEY",
keyName: "claude",
-16
View File
@@ -1,17 +1,5 @@
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;
@@ -35,10 +23,8 @@ 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
@@ -154,7 +140,6 @@ 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
@@ -246,7 +231,6 @@ 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,7 +17,6 @@ export const defaultState: AppState = {
deterministic: true,
debug: false,
timeout: 180,
http: "auto",
think: "",
apitype: "chat",
useFiles: false,
-1
View File
@@ -51,7 +51,6 @@ export interface AppState {
deterministic: boolean;
debug: boolean;
timeout: number;
http: "auto" | "r2" | "curl";
think: string;
apitype: OllamaApiType;
useFiles: boolean;
+2 -11
View File
@@ -5,18 +5,9 @@ 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 " + expandHome(dir));
r2.cmd("'touch " + expandHome(file));
r2.cmd("'mkdir -p " + dir);
r2.cmd("'touch " + file);
}
export function fileExists(path: string): boolean {
+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-sonnet-5-5
[0x000010a0]> r2ai -e model=claude-3-7-sonnet-20250219
[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-sonnet-5-5");
pj_ks (pj, "model", R_STR_ISNOTEMPTY (args->model)? args->model: "claude-3-7-sonnet-20250219");
}
pj_kn (pj, "max_tokens", args->max_tokens? args->max_tokens: 4096);
if (args->thinking_tokens >= 1024) {
+2 -13
View File
@@ -96,8 +96,6 @@ 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);
@@ -145,9 +143,7 @@ static RThreadFunctionRet finish(R2AITask *t, R2AITaskState st, char *err) {
return R_TH_STOP;
}
/* 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. */
// run one llm turn, r2ai_llmcall and the providers still read core->config
static R2AI_ChatResponse *run_llm_once(R2AITask *t) {
char *error = NULL;
R2AIArgs args = {
@@ -158,9 +154,7 @@ static R2AI_ChatResponse *run_llm_once(R2AITask *t) {
.tools = t->tools,
.error = &error,
};
RCorePluginSession worker_session = *t->cps;
worker_session.core = t->llm_core;
R2AI_ChatResponse *res = r2ai_llmcall (&worker_session, args);
R2AI_ChatResponse *res = r2ai_llmcall (t->cps, args);
if (error) {
task_lock (t);
free (t->error);
@@ -329,11 +323,6 @@ 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: "");
+1 -1
View File
@@ -1,5 +1,5 @@
project('r2ai', 'c',
version : '1.5.4',
version : '1.5.2',
default_options : ['warning_level=2', 'c_std=c11']
)
+1 -1
View File
@@ -28,7 +28,7 @@
#define r_core_call(x,y) r_core_cmd_call(x,y)
#endif
#define R2AI_VERSION "1.5.4"
#define R2AI_VERSION "1.5.2"
#define R2AI_DEFAULT_MODEL "gpt-oss:20b"
#define R2AI_DEFAULT_PROVIDER "ollama"
+1 -4
View File
@@ -52,7 +52,6 @@ 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;
@@ -83,9 +82,7 @@ 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 >= 160
return r_cons_editor (cons, file, str, 0, NULL);
#elif R2_ABIVERSION >= 120
#if R2_ABIVERSION >= 120
return r_cons_editor (cons, file, str, NULL);
#else
return r_cons_editor (cons, file, str);