Compare commits

...
744 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
pancake 20a3751624 Release 1.5.0 2026-10-06 00:45:47 +02:00
pancake d81957d856 Load API keys from environment as fallback 2026-10-03 08:56:09 +02:00
pancake b6d7c9424c Fix #130 - deterministic mode for r2ai 2026-10-02 18:21:35 +02:00
pancake c3efc53c27 Deduplicate system prompt fallback and message queue logic 2026-10-02 18:11:26 +02:00
pancake b6d234c154 Fix #172 - Add r2ai.history and store all queries in its context 2026-10-02 17:59:22 +02:00
pancake ef7f49a68e Keep the gemini auth header selection in one expression 2026-10-02 17:55:07 +02:00
pancake d1fcea6859 Share request building, parsing and posting across the providers
Gemini and Vertex Gemini share the request builder and response parser,
Anthropic and Vertex Anthropic share the request builder, and every
provider posts through r2ai_post, which centralizes the payload dumps,
the curl debug output and the error reporting.

Gemini now sends the system prompt as systemInstruction and reports
token usage. Also drops the unused streaming stubs and the dead
r2ai.system fallbacks, r2ai_llmcall always sets the system prompt.
2026-10-02 17:55:03 +02:00
pancake 96dcc0dd4f Dedup the decompilation block builder in r2ai -d and fix the prompt leak 2026-10-02 17:54:59 +02:00
pancake 659e14a4cd Dispatch providers through r2ai_send and look them up once
Rawtools now reaches every provider instead of only anthropic/openai,
and the model listing shares the header logic, sending the Gemini key
in a header instead of the URL.
2026-10-02 17:54:56 +02:00
pancake 926ef545d0 Share the conversation log formatter between -L and the compact prompt 2026-10-02 17:54:54 +02:00
pancake 1b11c648de Fix tool_call_id leak when recording auto mode tool responses 2026-10-02 17:54:51 +02:00
pancake 394e55cf87 Dedup message cloning and drop the unused json message parsers 2026-10-02 17:54:44 +02:00
pancake 8db446cf31 Dedup the async task lifecycle and command helpers
Add task_start, finish, cancelled, wait_tool, lookup and print_block
helpers, and drop the async queue guards since r2ai_init always
creates it.
2026-10-02 17:54:00 +02:00
pancake 65d7d87539 Rewrite the RJson to string converter as a single recursive emitter 2026-10-02 17:53:29 +02:00
pancake 372b38380f Share the tool confirmation prompt between r2cmd and execute_js 2026-10-02 17:53:24 +02:00
pancake 72b9ff2f49 Add ask and enqueue helpers to dedup the llm query paths 2026-10-02 17:53:16 +02:00
pancake 742d3f2a51 Unify the vdb query commands and drop the load_embeddings copy
-S and -Rq share cmd_r2ai_R, which honours r2ai.data.nth and prints
through r_cons. Also fixes the leaked file contents when indexing.
2026-10-02 17:53:01 +02:00
pancake 1f3f93fa35 Turn leftover auto mode debug prints into debug logs 2026-10-02 13:49:33 +02:00
pancake 3f1b91fbce Do not write the OpenAI bearer token to the debug log 2026-10-02 13:49:25 +02:00
pancake acd0513ebb Prompt for final auto response, fixes out of order logs 2026-10-02 13:48:55 +02:00
pancake c44e838238 Fix bogus format argument when logging the models request 2026-10-02 13:48:22 +02:00
pancake f4fa4ba432 Preserve negative integers when converting RJson to PJ 2026-10-02 13:46:49 +02:00
pancake 561db876ac Fix malformed bold escape sequences in auto mode output 2026-10-02 13:46:46 +02:00
pancake bcdc182d03 Snapshot tools and max_runs for async tasks on the main thread 2026-10-02 13:46:39 +02:00
pancake 19cba835e4 Remove leaking unreachable error paths in the Anthropic response parser 2026-10-02 13:46:32 +02:00
pancake 87a1e89112 Do not leak the Gemini API key in the request URL 2026-10-02 13:46:25 +02:00
pancake 012118c25c Fix Gemini headers and always send the Content-Type 2026-10-02 13:44:53 +02:00
pancake e2030b31a1 Escape r2ai.auto.init_commands when building for JSON 2026-10-02 13:44:39 +02:00
pancake 53f437d55a Keep assistant replies in the conversation log when clippy is enabled 2026-10-02 13:40:05 +02:00
pancake 66f202254e Fix prompt in r2ai one-shot mode and report errors 2026-10-02 13:39:42 +02:00
pancake 8e9555d144 Fix double advance on unterminated ${ and $( in prompt variables 2026-10-02 13:39:36 +02:00
pancake a8caa51e76 Do not update vdb TF-IDF statistics when querying 2026-10-02 13:39:34 +02:00
pancake 021aa205f0 Initialize the document frequency of new vdb tokens 2026-10-02 13:39:31 +02:00
pancake 92c86f94e6 Fix message leak on success in r2ai_msgs_from_json 2026-10-02 13:39:26 +02:00
pancake 50c1d63a47 Use text from the editor when confirming multiline tool calls 2026-10-02 13:35:30 +02:00
pancake 79f01945b7 Fix memleak when parsing r2ai rc file 2026-10-02 13:35:16 +02:00
pancake 9043638e01 Fix UAF in r2ai -d 2026-10-02 13:35:05 +02:00
pancake 6bd0227382 Render responses in markdown by default 2026-10-02 13:20:23 +02:00
pancake 9f9a3b87ae Fix token usage leaks after LLM calls 2026-09-07 00:44:43 +02:00
pancake b68ddaeaed Fix thread ownership race in r2ai_async_fini 2026-09-06 21:43:15 +02:00
pancake d2663f75dc Fix null deref when adding a toolCall 2026-09-06 21:41:24 +02:00
pancake e24dbab677 Release 1.4.4 2026-09-06 21:23:13 +02:00
pancake c765269b02 Add allocation checks and use calloc for KD-tree nodes 2026-09-06 21:13:54 +02:00
pancake 55b856e70b Refactor LLM call logic and improve tool call handling 2026-09-06 21:13:50 +02:00
pancake 5e148bc882 Null checks and improve error handling for curl and powershell 2026-09-06 21:13:42 +02:00
pancake 9f93754483 Fix baseurl with ending slashes 2026-08-19 16:22:42 +02:00
pancake d262fabb2b Add r2plugin abstraction for decai 2026-08-18 17:07:35 +02:00
pancake d0a1f292f5 Release 1.4.2 2026-08-16 21:26:19 +02:00
pancake dad02ac243 Fix OpenAI-compatible endpoint selection for Ollama 2026-08-05 00:42:46 +02:00
dependabot[bot] 02c561c415 Bump radareorg/github-actions from 2 to 3
Bumps [radareorg/github-actions](https://github.com/radareorg/github-actions) from 2 to 3.
- [Release notes](https://github.com/radareorg/github-actions/releases)
- [Commits](https://github.com/radareorg/github-actions/compare/v2...v3)

---
updated-dependencies:
- dependency-name: radareorg/github-actions
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-04 18:21:02 +02:00
pancake 9414cdf72b Fix build for latest r2 (abiversion 125) 2026-07-22 18:04:38 +02:00
pancake 7cc61c4cc5 Generic core editor helper 2026-07-16 16:13:17 +02:00
pancake e4875eeb37 Fix build with r2 from git 2026-07-16 13:38:30 +02:00
pancake 888251a2ea Improve kvcache misses and improve token usage 2026-06-28 19:14:33 +02:00
pancake af6153ad64 Support r2ai.apitype (also in decai) to specify generate/chat 2026-06-28 19:14:29 +02:00
dependabot[bot] 87e4e7b232 Bump actions/checkout from 6 to 7 2026-06-22 16:18:17 +02:00
pancake cc3a003a64 Cleanup the openai implementation 2026-06-17 18:07:49 +02:00
pancake f2be5cc3c2 Release 1.4.0 2026-06-03 09:53:26 +02:00
dependabot[bot] e360148931 Bump radareorg/github-actions from 1 to 2 2026-05-18 09:43:20 +03:00
pancake d06a9ee361 Implement -d in the r2ai shell to decompile from the cli 2026-05-14 21:41:20 +02:00
pancake 2e7b97b065 Add r2ai -s to seek on start 2026-05-14 21:34:28 +02:00
orbisai0security cd5d9a1640 Fix shell injection in the abandoned python implementation 2026-05-14 08:18:20 +02:00
Ole André Vadla Ravnås e606392aaf Emit libr2ai.a for embedders
meson uses library() so -Ddefault_library picks the kind.
Makefile also runs ar rcs and installs libr2ai.a + r2ai.h.
2026-05-09 13:11:07 +02:00
pancake 8bf1169844 Implement r2ai -do to decompile with offsets 2026-05-06 13:15:32 +02:00
pancake 90e3bbafc5 Format code 2026-05-05 20:05:56 +02:00
pancake e5a0639b88 Remvoe support for legacy query prompts 2026-05-05 20:02:23 +02:00
pancake 80f27cd4d9 Add -qj to list query prompts in json format 2026-05-05 19:56:40 +02:00
pancake 455da7bdba Improve query prompts syntax and parser 2026-05-05 19:49:18 +02:00
pancake 623e98ed3d Show tool command to be executed when prompting 2026-05-05 19:16:50 +02:00
pancake 17bf56b5e3 Add explicit aprove/reject actions for async tasks 2026-05-05 18:27:58 +02:00
pancake f50a7836eb Initial support for r2plugin xps 2026-05-05 14:03:40 +02:00
pancake 2d9b69c624 Reduce tool calling parameters using the new ToolResult struct 2026-05-05 13:54:48 +02:00
pancake de9c7c904c Auto prompt is no longer a global 2026-05-05 11:32:57 +02:00
pancake 6b3ea982cd Better structure for r2ai-sj 2026-05-05 11:13:50 +02:00
pancake 209d9257ee Use RCoreHelpMessage in async commands 2026-05-05 11:11:46 +02:00
pancake b840d002ee Fix #226 - R2AI_HEADERS and r2ai.http.headers 2026-05-05 10:56:44 +02:00
pancake 9d900b2a6b Fix autopurge in -sj/-s and rename -s*/-sp 2026-05-05 06:45:23 +02:00
AsherDLL 227b15e793 Add Vertex AI provider for Gemini and Anthropic models 2026-04-24 13:49:40 +02:00
pancake 4b9dbf7686 Fully go for RCore.call 2026-04-21 19:48:49 +02:00
pancake 8ca32e258f Add async.purge option and fix some thread locks 2026-04-20 20:56:16 +02:00
pancake 449c41eae6 Format the code again 2026-04-20 20:28:40 +02:00
pancake 5210fe6892 Initial support for async tasks 2026-04-20 20:26:36 +02:00
pancake 6222791075 Add SOUL.md/IDENTITY.md personality files via -id command 2026-04-14 20:48:50 +02:00
pancake 05ce908e3a Fix ollamacloud api arguments object error 2026-04-14 19:07:50 +02:00
pancake b8457c0bfa Release 1.3.8 2026-04-13 22:46:10 +02:00
pancake 6a256b9210 Refresh auto tool list when r2ai.auto.usejs changes 2026-04-13 22:45:30 +02:00
pancake 8cc748ab4c Honor per-call model/provider overrides in LLM backends 2026-04-13 22:35:18 +02:00
pancake 17df397a3d Fix argument parsing to make -Lj actually work 2026-04-13 19:54:42 +02:00
pancake fcd464f1cc Fix OpenAI debug crash with keyless backends 2026-04-13 19:48:08 +02:00
pancake 1f41b6d2d7 Fix getopt parsing for -i, -dr and -Rq 2026-04-13 19:43:37 +02:00
pancake adf8189541 Fix UB in r2ai_message_free error paths 2026-04-13 18:01:24 +02:00
dependabot[bot] cb8f3548ff Bump softprops/action-gh-release from 2 to 3 2026-04-13 18:00:24 +02:00
pancake b99034e5d8 Fix user message not included when the message list was empty 2026-04-12 21:17:53 +02:00
pancake fa1f6c1d3c Fix few logic mistakes swapping name/desc and such 2026-04-12 21:16:01 +02:00
pancake d9fb7b18df Fix duplicated user message then r2ai.data=true 2026-04-12 21:14:01 +02:00
pancake e0b9ff235c Fix invalid free crashes on tool call block 2026-04-12 21:00:38 +02:00
pancake e1bf58d4e4 Some better variable names for decai 2026-04-12 18:40:34 +02:00
pancake 51a63ac7ef Remove unused and duplicated logic using helpers where needed 2026-04-12 18:35:07 +02:00
pancake c3a3eff046 Single helper to check if function is available in decai 2026-04-12 18:11:18 +02:00
pancake 37b5c2b6d0 Fix thinking level issue causing slow queries in r2ai 2026-04-12 11:53:35 +02:00
pancake 0e6a8cc2e2 In decai fix thinking options for glm models making a clear time improvements 2026-04-12 11:42:55 +02:00
pancake 8dcc6f0418 Release 1.3.6 2026-04-11 18:06:11 +02:00
pancake b68cdd1171 Fix empty query bug in r2ai -i 2026-04-10 16:00:58 +02:00
pancake 3f935a46be Do not silently discards caller-provided message history 2026-04-10 15:53:19 +02:00
pancake fd46390d41 Break auto mode when receiving unknown actions 2026-04-10 15:45:42 +02:00
pancake 63106508dc In decai: Restore model state when decopipe execution fails 2026-04-10 15:45:00 +02:00
pancake 46f42d3dd9 Ignore comment lines in decai.txt (decai -E) 2026-04-09 21:39:08 +02:00
pancake 836fae3d91 Use github actions to install r2 in the ci 2026-04-09 11:02:00 +02:00
pancake 66dd3dced7 Oops missing file 2026-04-09 10:44:16 +02:00
pancake c016fc72ed Release 1.3.4 2026-03-31 18:32:01 +02:00
Abhi 779b787f2d Fix URL formatting for OpenAI compatible API 2026-03-31 16:49:22 +02:00
pancake ca210f149b Add zen, ollamacloud and lmstudio providers to r2ai, sync with decai 2026-03-30 17:46:10 +02:00
pancake 99e99b1797 Reduce some LOCs in decai 2026-03-30 16:51:16 +02:00
pancake fb5427238b Add 'decai -E' for the defaults 2026-03-30 16:45:46 +02:00
pancake d7c2dacd7a Add decai timeout, zen provider, improve prompt, custom headers and more 2026-03-30 16:38:10 +02:00
pancake d6c32f4b72 decai: tighten types and simplify provider/config handling 2026-03-30 15:26:23 +02:00
dependabot[bot] 0a083e7231 Bump actions/download-artifact from 4 to 8 2026-03-30 15:20:45 +02:00
dependabot[bot] 1a067fbbfc Bump softprops/action-gh-release from 1 to 2 2026-03-30 15:18:54 +02:00
dependabot[bot] 8a724d3682 Bump actions/upload-artifact from 4 to 7 2026-03-30 15:18:30 +02:00
dependabot[bot] 8f7d152aa3 Bump actions/checkout from 5 to 6 2026-03-30 15:17:48 +02:00
pancake 7120c325da Add dependabot 2026-03-30 13:47:53 +02:00
pancake 14c991cd10 Update ci actions 2026-03-30 13:47:16 +02:00
pancake d1ef1e065e Welcome to the initial r2clippy wizard 2026-03-30 13:45:06 +02:00
pancake 6f8ec21a39 Release 1.3.2 2026-03-19 14:47:39 +01:00
pancake 3db0744c91 Fix shell injection in curl command construction 2026-03-07 12:20:28 +01:00
pancake 3306e412e2 rawtools: require explicit TOOL prefix for tool execution 2026-03-07 12:20:13 +01:00
pancake 20f5e16164 fix: detect multiline after adding command annotations 2026-03-07 12:20:03 +01:00
pancake 660249a31f Fix double free and refactor the complex logic into a separate function 2026-03-06 17:03:30 +01:00
Nitan Alexandru Marcel 312921c2a1 Add r2agent reference in the README 2026-02-08 23:32:54 +01:00
pancake 2356dbef9e Release 1.3.0 2026-01-04 19:17:05 +01:00
pancake 7612a80a9c Warn about invalid syntax/lines in apikeys.txt 2025-12-10 20:05:55 +01:00
pancake 12d8bc9e22 Strip "_API_KEY" from providers in apikeys.txt 2025-12-10 20:03:25 +01:00
pancake d4be5f8569 Add .pkgname for the r2ai plugin 2025-12-10 01:01:45 +01:00
pancake a9333c8c38 Only one function doing curl and remove aliased providers in decai 2025-12-06 12:36:04 +01:00
pancake 29bdb2fcea Remove the hardcoded model names for providers with callback 2025-12-05 17:48:42 +01:00
pancake 078376c5d2 Refactor the model listing code in decai 2025-12-05 17:30:11 +01:00
pancake aa24702335 Refactor the providers logic in decai 2025-12-05 17:05:18 +01:00
pancake 9aa40216a2 Initial rewrite of decai in TypeScript 2025-12-05 16:44:09 +01:00
pancake 96e454e0c6 Fix tool calling message construction bug 2025-11-27 11:07:16 +01:00
pancake 219b034261 Don't feed system messages to Anthropic 2025-11-21 21:23:24 +01:00
pancake 797c967fb0 Remove redudant provider fields 2025-11-11 16:21:55 +01:00
pancake e61b7db8a5 Fix #219 - Add deepseek provider for r2ai.c 2025-11-11 16:03:10 +01:00
pancake 3a39f9cd9d Add decai -b to setup the baseurl 2025-11-07 00:25:59 +01:00
pancake 2b5e50d8f0 Workaround MANDIR for old r2 2025-11-06 16:36:45 +01:00
pancake bb737f5be6 Add decai -K to edit apikeys (requires latest r2 from git) 2025-11-06 16:33:05 +01:00
pancake 3e5f91e631 Load decai apikeys from ~/.config/r2ai/apikeys.txt 2025-11-06 14:19:11 +01:00
pancake a3154bdcaf Simplify and fix the gemini support in decai 2025-11-06 14:10:19 +01:00
pancake a6be6e6555 Add decai -p to select provider 2025-11-06 14:01:53 +01:00
pancake ad0bdbb902 Fix res error in gemini for decai 2025-11-06 13:44:55 +01:00
pancake 4651d3b1b3 Update the README 2025-11-05 17:40:51 +01:00
pancake 572e02c4ec Change symbol visibility to please Windows builds 2025-11-05 17:18:50 +01:00
pancake 75c787ef41 Fix the meson build 2025-11-05 12:52:25 +01:00
pancake 731f1933a9 Refactor the apikeys code into a separate file and use sample config key by default 2025-11-05 12:43:15 +01:00
pancake 573755ccef Add -e to r2ai main 2025-11-01 21:30:12 +01:00
pancake ecca7825fe Add r2ai -i to run scripts 2025-10-31 20:15:32 +01:00
pancake 95117608fb Small improvements for the clippy repl 2025-10-31 20:13:30 +01:00
pancake b3958b5f7d Initial clippy repl for the r2ai cli tool 2025-10-31 19:45:12 +01:00
pancake d0eaf37764 Fix -m? reported crash 2025-10-31 16:34:49 +01:00
pancake 65692edd54 Bring back the support for Gemini provider to r2ai.c 2025-10-30 20:08:33 +01:00
pancake 50f55bb5d2 Add r2ai -K to edit api keys and remove the unnecessary compatdb 2025-10-29 22:21:33 +01:00
pancake fe1768fcc3 Fix apikey error message 2025-10-29 16:50:30 +01:00
pancake f241fba17d Add WIP decai logo 2025-10-29 10:45:55 +01:00
pancake e5252960e0 Implement r2ai.auto.slim to make disasm slimmier for auto 2025-10-29 10:45:21 +01:00
pancake 8a45dadbde Rename r2ai.auto.hide_tool_output to r2ai.auto.verbose 2025-10-29 10:34:31 +01:00
pancake 53d32efbbe Deprecate r2ai.chat.show_cost 2025-10-29 10:28:48 +01:00
pancake f06faba9d6 Release 1.2.6 2025-10-29 00:02:52 +01:00
pancake 8b52c2f6e2 Fix ninja build 2025-10-28 23:55:39 +01:00
pancake b37436fb8d Fix more warnings 2025-10-28 23:41:45 +01:00
pancake 2524e66825 Fix warning unused variable 2025-10-28 23:35:41 +01:00
pancake 519017a75e Fix editing r2js scripts in auto mode 2025-10-28 23:34:05 +01:00
pancake 6f30d6ca37 Handle comments in commands in all the auto calls 2025-10-28 20:49:24 +01:00
pancake 08ea4d11b2 Better last call 2025-10-28 19:51:22 +01:00
pancake 0281308434 Fix the build 2025-10-28 19:42:11 +01:00
pancake 17a59d2057 Add r2ai.debug to see the llm requests and responses 2025-10-28 19:37:01 +01:00
pancake eac0b8c6ef Prompt before leaving the interrupted auto mode 2025-10-28 19:30:11 +01:00
pancake 24aee426b4 Comments in commands are now used as annotations for the conversation 2025-10-28 19:28:05 +01:00
pancake b31bc5c116 Suggested simpler auto prompt 2025-10-28 19:24:40 +01:00
pancake 01738210f5 Increase timeout to 4 minutes 2025-10-28 19:13:05 +01:00
pancake 7b19c4ff99 Resizing the terminal is no longer interrupting the network calls 2025-10-28 19:10:50 +01:00
pancake 1986dd88bd r2ai needs an argument to work 2025-10-28 12:57:20 +01:00
pancake 1ed40401dd Implement r2ai -C to compact the message log 2025-10-28 12:55:46 +01:00
pancake 6ac0ff6352 Add compact prompt 2025-10-28 12:17:49 +01:00
pancake 4158e5f157 Add an option to disable the js_execute tool 2025-10-28 00:05:30 +01:00
pancake 225ff58228 Add r2ai.auto.think to disable reasoning 2025-10-28 00:03:34 +01:00
pancake a51be44c68 Improve native tool calling prompt 2025-10-28 00:00:04 +01:00
pancake 911e455f72 Only capture ONE tool call from the auto response 2025-10-27 23:40:03 +01:00
pancake 70eb975b7f Fix the set_prompt for auto mode 2025-10-27 23:21:58 +01:00
pancake a0d36cc909 Implement the -LR command to create a log report 2025-10-27 23:08:51 +01:00
pancake 030551816b Fix windows build 2025-10-27 23:08:51 +01:00
pancake 88c44c871b Fix tool calling, at least for OpenAI 2025-10-27 23:08:51 +01:00
pancake 9218e3ea27 Capture the thinking 2025-10-27 23:08:51 +01:00
pancake 6f7ffcae1a Oh great now we have a bug in the json serializer of r2 2025-10-27 23:08:51 +01:00
pancake fe12d2f8ee Add r2ai -b for the shell 2025-10-27 23:08:51 +01:00
pancake ae55b2f204 Proper use of RList -20LOC 2025-10-27 23:08:51 +01:00
pancake 7d4e949240 Use RList instead of hardcoded NEWS vectors 2025-10-27 23:08:51 +01:00
pancake 9b97d39c7a Add r2ai -b to change the baseurl 2025-10-27 19:04:16 +01:00
pancake 33f72a997e Refactorings to avoid hardcoded ansi colors. Using Color macros instead 2025-10-27 18:47:49 +01:00
pancake 36a8f13131 Use R2_CURL on Windows 2025-10-27 18:29:01 +01:00
pancake 5c2ded0171 Fix r2ai -E and loading script 2025-10-27 17:46:18 +01:00
pancake d5a39816a8 Use R2__WINDOWS__ instead of defined(_WIN32) 2025-10-27 13:02:36 +01:00
pancake 5aab38ab76 Proper command escapes for curl and pwsh http backends 2025-10-27 13:00:35 +01:00
pancake 9f15ec6c73 Release 1.2.4 2025-10-26 15:04:03 +01:00
pancake 68baac9911 Transform all promptfiles into the new md format 2025-10-26 15:02:48 +01:00
pancake 54a280952e Load API keys from ~/.config/r2ai/apikeys.txt too 2025-10-26 14:46:20 +01:00
pancake efc2368af1 Move the examples into the attic 2025-10-26 14:31:46 +01:00
pancake 28ddcbf638 Refactor and cleanup the http.backend implementations 2025-10-26 14:04:58 +01:00
pancake 85e9ed5c65 Do not accept invalid values in r2ai.http.backend 2025-10-25 19:56:10 +02:00
pancake 8886365925 Release 1.2.2 2025-10-25 11:03:12 +02:00
pancake 01d8e29420 Remove the r2-5.x compat macros 2025-10-25 11:02:39 +02:00
pancake e8b556ee5d Use r_sys_sleep and simplify the overengineered retry_sleep logic 2025-10-25 10:50:05 +02:00
pancake 6c336f48f7 Fix the meson build 2025-10-24 02:35:34 +02:00
pancake 53d61831b2 Initial support for vscode stype prompt files 2025-10-24 02:31:03 +02:00
pancake cbcff6b9dd Add r2ai -E in the cli 2025-10-24 02:29:34 +02:00
pancake f04bf8162e Add the manpage for r2ai.1 2025-10-24 02:04:36 +02:00
pancake 0206ceb390 Add support for r2ai -E and ~/.config/r2ai/rc 2025-10-24 02:00:15 +02:00
pancake ff1f788c48 Minor fix 2025-10-24 01:36:58 +02:00
pancake df135b2c70 Use jan-nano by default and fix the undefined in decai 2025-10-24 01:27:24 +02:00
pancake c152777bbe Dont do rawtools by default yet 2025-10-23 19:06:09 +02:00
pancake 3e084ce797 Implement r2ai.auto.raw to use tool calling with non-tools models 2025-10-23 02:12:36 +02:00
pancake 7fce8788b5 Release 1.2.0 2025-10-21 20:49:18 +02:00
pancake 585e30470e Aim to fix the execute_js tool for r2ai -a 2025-10-21 20:48:28 +02:00
pancake 5594e64ef3 r2ai-q must be public on windows 2025-10-21 20:44:07 +02:00
pancake 904a356290 Update READMEs 2025-10-21 20:40:17 +02:00
pancake d1e2bcf911 Use the powershell http fallback when needed 2025-10-21 20:38:10 +02:00
pancake 21528f7ea8 Use ollama/gpt-oss:20b as default provider/model pair 2025-10-21 20:34:58 +02:00
pancake 2883d134aa Add a WIP decompiler prompt 2025-10-21 20:18:58 +02:00
pancake ec96ba3329 Improve the behaviour of 'r2ai -m' to show, list and select models 2025-10-21 20:12:43 +02:00
pancake f92ea17b01 Move r2ai -n into r2ai -q autoname 2025-10-21 20:07:03 +02:00
pancake 2dc92031e7 Move r2ai -v into r2ai -q varnames 2025-10-21 20:03:16 +02:00
pancake 55d54b4953 Move r2ai -s into r2ai -q signature 2025-10-21 19:58:16 +02:00
pancake 35460e639c Move r2ai -x into r2ai -q explain 2025-10-21 19:49:47 +02:00
pancake a5382c17c4 Move r2ai -V to 'r2ai -q vulns' 2025-10-21 19:41:10 +02:00
pancake f4bdb847db Fix null deref when ^C in r2ai 2025-10-21 19:40:40 +02:00
pancake a7c3d3767c Use r2 -NNH instead of r2 -H to fix build when r2 have broken plugins 2025-10-21 19:29:09 +02:00
pancake 97c47ab28a Add -m, -p, -q to choose model, provider and query from the cli
* Fix the ollama provider
2025-10-21 19:24:11 +02:00
pancake c36bc1500c Dont pass R2AIState if we can pass the RCorePluginSession 2025-10-21 18:58:16 +02:00
pancake 179cb6ca58 Remove statistics global variable 2025-10-21 18:45:49 +02:00
pancake 402c31cc96 Handle -v and -h in the r2ai executable 2025-10-21 18:32:47 +02:00
pancake be14729424 Stop using the deprecated r_cons_singleton 2025-10-21 18:28:09 +02:00
pancake aa9b8e0fba Refactoring with the aim to reduce LOCs in r2ai_http.c 2025-10-21 18:24:09 +02:00
pancake 96225cd555 Update AGENTS.md 2025-10-21 17:45:39 +02:00
pancake 321ca1bb41 Move the LLM related functions from r2ai.c into llm.c 2025-10-21 17:29:14 +02:00
pancake d94ed5a088 Fix r2ai's main and use cps everywhere 2025-10-21 16:40:57 +02:00
pancake 3537d006de Added support to query custom user prompts in r2ai 2025-10-21 00:10:28 +02:00
pancake bdcee9e736 Remove global states, requires r2-6.0 at least 2025-10-20 21:54:48 +02:00
pancake 074b7a6c9f Add AGENTS.md for r2ai.c 2025-10-20 21:25:24 +02:00
pancake 6697df8437 Use RList instead of vector for messages. Fix message count in r2ai -L 2025-10-20 21:04:30 +02:00
pancake ff0fb4c802 Add version information in decai help message and use rcons 2025-10-20 19:49:40 +02:00
pancake ca7d02e03c Refactor the api/providers logic for easier maintainance 2025-10-20 17:35:43 +02:00
pancake bd105b2b0e Fix #176 - Do not accept invalid values in "r2ai -e api=" 2025-10-20 16:57:53 +02:00
pancake 5e18525403 Use clang-format-radare2 instead of the hacky indent.py 2025-10-20 16:48:42 +02:00
pancake c9d0756e74 Fallback to powershell when curl is not found on Windows 2025-10-20 11:05:20 +02:00
Abhi d3fc79f9d1 Fix tmp dir on android(termux) 2025-10-14 09:27:50 +02:00
pancake 8f4c972beb Remove pkg-config hard dependency for r2 >= 6.0.5 2025-10-08 13:51:34 +02:00
pancake f95cda42f7 Release 1.1.2 2025-10-06 23:25:27 +02:00
pancake b241f30432 Fix leak in openai_url 2025-10-06 23:25:01 +02:00
pancake 8eb50e466b Include the r2ai plugin in the meson ci builds 2025-10-06 23:22:01 +02:00
pancake 14b5dcbfb9 Fix make install and warnings 2025-10-06 22:59:22 +02:00
cryptax e264f8f0c8 More references In the README.md 2025-10-06 15:45:10 +02:00
pancake 46c2ce6c62 Fix r2ai install when old symlinks are around 2025-10-03 22:34:36 +02:00
pancake 693ad9270c Handle ollama errors 2025-10-03 18:21:35 +02:00
pancake 8446af7385 Add description to all the config vars 2025-10-03 15:35:48 +02:00
pancake 5a156a0126 Do not use temperature for the openai provider in decai 2025-10-03 15:35:43 +02:00
pancake a28efa7a33 Add config var descriptions (fixes e??) 2025-10-03 12:46:25 +02:00
pancake bc5adb3fcb Refactor the model listing logic in decai 2025-10-02 14:00:30 +02:00
pancake 4bd1d911e0 Bring back the XAI Provider 2025-10-02 13:28:17 +02:00
pancake 41c9734c69 Bring back the mistral provider 2025-10-02 13:25:31 +02:00
pancake 1a41298dd6 Bring back the gemini provider 2025-10-02 13:22:47 +02:00
pancake c618f8ed09 Bring back the readable prompts 2025-10-02 13:20:42 +02:00
pancake 48932c5c64 Add support for listing models from openai endpoints in the decai 2025-10-02 13:17:08 +02:00
pancake 4da20a5c33 Bring back the OpenAI backend for Decai 2025-10-02 11:23:21 +02:00
pancake b7f94ad419 Release 1.1.1 2025-10-01 15:48:01 +02:00
pancake 2e45fb9e9c Fix spaces 2025-10-01 15:44:20 +02:00
pancake 9775e94455 Add support for ollamacloud to decai 2025-10-01 12:16:12 +02:00
pancake 67416766e2 Move the benchmark scripts into its own directory 2025-10-01 12:07:42 +02:00
pancake 5c1be40048 Improve benchmark2html.sh and makefile 2025-10-01 11:00:13 +02:00
pancake 49e87c7b7a Add script to generate benchmark results in html 2025-10-01 10:22:37 +02:00
pancake 11e7c288e0 Include decai.r2.js in the release page 2025-10-01 02:37:59 +02:00
pancake 909f4738f8 Release 1.1.0 2025-10-01 02:33:36 +02:00
pancake 0148a64aa7 Add decai to the release pack 2025-10-01 00:17:34 +02:00
Konstantinos Xynos d8df27a4c7 Refactored decai.r2.js to reuse providers implementations
* Update decai.r2.js to support local gpt4all
* Added support for local API server gpt4all by Nomic
* Added maxTokens support to GPT4all
* Added to GPT4all, temperature as 0.1 to enable deterministic option
* Rafactored using Claude. Needs to be tested
2025-10-01 00:09:18 +02:00
pancake 6608dbcf3c Add the Windows and macOS CI builds 2025-10-01 00:07:56 +02:00
pancake ef83ddefc9 Add r2 badge 2025-09-30 13:12:16 +02:00
pancake c19fd205e3 Make in root wraps the one in src 2025-09-30 12:51:39 +02:00
pancake eec673f8db Improved readme again 2025-09-30 12:29:27 +02:00
pancake 5e20b37e08 Remove python lint ci jobs 2025-09-30 12:15:59 +02:00
pancake 547418adf0 Move python and server stuff into the Attic 2025-09-30 12:00:23 +02:00
pancake e161577bcb Update README 2025-09-30 12:00:17 +02:00
pancake ba186d7c57 Move the getapikey logic into a separate function in r2ai.c 2025-09-26 03:08:49 +02:00
pancake 24c1e3fcb4 Suggest setting the API key with env var instead of the deprecated eval vars 2025-09-25 19:06:45 +02:00
pancake 1f001080ff Use r2-6.0.2 2025-08-20 01:38:51 +02:00
pancake a189a682b6 Disable the python ci job 2025-08-19 21:04:03 +02:00
cryptax a0299f791d Add support for gpt-5 2025-08-19 12:09:46 +02:00
cryptaxandcryptax 79214fb22c Fix to support lm-studio servers with openai style api and baseurl (#196)
Co-authored-by: cryptax <axelle@alligator>
2025-08-18 20:43:31 +02:00
cryptax 775d3243e6 removing port 2025-08-18 16:32:42 +02:00
pancake cd514ac099 Run indent 2025-08-18 12:24:58 +02:00
cryptax 6f352e1966 Support r2ai.baseurl having a custom port 2025-08-18 12:24:58 +02:00
cryptax 32458a653e Documentation for r2ai.port 2025-08-18 12:24:58 +02:00
cryptax b32fd6097f Fix Ollama calls using /chat instead of /chat/completions 2025-08-18 12:24:58 +02:00
cryptax df2fc67e2c Fixing list models for Ollama case 2025-08-18 12:24:58 +02:00
pancake b318c30865 Fix support for gpt-5 in decai 2025-08-14 14:27:08 +02:00
pancake 2610309b58 Release r2ai-1.0.0 2025-08-01 13:19:29 +02:00
pancake 0b81158d01 Rebrand r2ai.host into r2ai.baseurl in r2ai.c 2025-08-01 13:16:52 +02:00
pancake 4bcd2c582d Replace host/port configs with baseurl in decai 2025-08-01 13:08:15 +02:00
pancake 51aac5f348 Rename r2ai.base_url to r2ai.host for consistency with decai 2025-07-11 20:37:34 +02:00
pancake a437938526 Fix r2ai.c for the latest r2 2025-07-11 12:48:34 +02:00
pancake c6bba6c7dd Minor code improvements 2025-07-01 18:55:00 +02:00
pancake 3103df8210 Fix build 2025-06-27 21:38:22 +02:00
pancake b1fd4aab8e Honor use_files in system backend 2025-06-27 17:04:18 +02:00
pancake 26ca088c32 Fix build for old r2 2025-06-27 09:15:41 +02:00
pancake 710eb93bbe More build fixes 2025-06-27 00:26:20 +02:00
pancake fb1f53a176 More oldr2 fixes 2025-06-26 20:12:14 +02:00
pancake 138fd5e7d3 More build fixes for old r2s 2025-06-26 19:54:33 +02:00
pancake bd040f9a48 More build fixes 2025-06-26 19:26:27 +02:00
cryptax 1f3f04d74b documenting options in r2ai src 2025-06-26 19:23:32 +02:00
cryptax a7cff9d970 adding possibility to ask a question on a given function in direct mode 2025-06-26 19:23:27 +02:00
cryptax a1de8828ac todo to fix completely edition of commands 2025-06-26 19:23:23 +02:00
cryptax 7a1d4668d6 minor debug log message modif 2025-06-26 19:23:19 +02:00
cryptax e20f390878 Fix: auto.init_commands may be edited interactively by the user so the real edited command must be shown in the context 2025-06-26 19:23:09 +02:00
cryptax be18fe91ad fixed context reset + remove max_input_tokens: tokens are not characters we cant just truncate does not make sense 2025-06-26 19:21:56 +02:00
cryptax 6df2a231eb Fixing max output tokens were not correctly read and set in Anthropic requests 2025-06-26 19:21:51 +02:00
pancake 668eee7807 Fix build for old versions of r2 2025-06-26 19:20:19 +02:00
pancake 111974071f Fix command injection vuln in decai causing syntax errors in auto responses 2025-06-26 14:46:12 +02:00
pancake b193caec61 Add http.files and system curl option for r2ai.c 2025-06-26 14:31:56 +02:00
pancake 8994415242 Fix build with latest r2 2025-06-26 05:15:52 +02:00
pancake 7e6953dce6 Add abi version for r2 >= 5.9.9 2025-06-21 00:45:46 +02:00
cryptax 0bcd68c434 adding max_input_tokens to auto 2025-06-20 15:29:39 +02:00
pancake 74e0b23ab0 Improve the auto repl with better help and control actions 2025-06-17 12:42:55 +02:00
pancake 1044bea8dd Use r2pipe2 for auto mode to capture stderr 2025-06-17 00:30:09 +02:00
pancake 081e34a835 Improved auto prompt 2025-06-16 23:33:02 +02:00
pancake 9f22b4c3c3 Simplify the auto prompt 2025-06-16 22:56:24 +02:00
pancake 4fb8ab0f2e Add DecaiTTS, Improve reasoning, auto prompt and planning 2025-06-16 22:38:35 +02:00
sha0coder 8e5b7e0152 Dockerfile for r2 + r2ai + decai + r2ghidra + nice .radare2rc (#181) 2025-06-10 00:09:40 +02:00
pancake e40c655013 Rename ask_to_execute to yolo in r2ai-c 2025-06-09 20:12:07 +02:00
cryptax 0ebda776cf remove static lists for providers where dynamic fetch works fine 2025-06-07 12:54:36 +02:00
daniel nakov 879592f22e Add -e model=? to list models 2025-06-04 20:08:49 +02:00
pancake fd3efa6c70 Use the claude endpoint instead of hardcoding available models 2025-06-03 22:21:21 +02:00
pancake b420645c20 Add local YOLO mode in decai auto 2025-05-28 18:56:32 +02:00
pancake 211e9af847 Support broken jsons in decai auto 2025-05-28 18:55:01 +02:00
pancake 58e1eb6d14 Add 'skip' action and improve prompt in decai auto 2025-05-28 17:55:25 +02:00
pancake c72fe14f19 Fix gemini support in decai 2025-05-28 17:43:30 +02:00
pancake 10162f7302 Add decai -e files to use files instead of argv 2025-05-28 17:43:16 +02:00
pancake 949e22fdf6 Add o3 and o4 models in decai 2025-05-28 16:43:52 +02:00
pancake 2355c15cd5 gpt4.1 fix with wrong json for decai auto 2025-05-28 16:23:43 +02:00
pancake c5db441a07 List last claude models in decai 2025-05-26 12:03:42 +02:00
pancake 9853a99bcb Add skynet-like r2ai images 2025-05-15 00:59:55 +02:00
pancake 4d685520b1 Improve ollama error message when a model is not available 2025-05-14 20:02:51 +02:00
pancake a20035fd31 Workaround for the empty dir.tmp bug in r2 2025-05-14 12:56:53 +02:00
pancake 733ebf9283 Release 0.9.6 2025-05-13 15:41:50 +02:00
pancake 595c0ef2d6 Fix decaiExplain with reasoning models 2025-05-12 18:54:26 +02:00
pancake c86b58a956 Implement r2 command output for decai -x[*] 2025-05-12 18:29:44 +02:00
pancake bd1f39b379 Force lang=C in decai -s function signatures 2025-05-12 18:22:58 +02:00
pancake 71bee067b3 Massage r2ai-c-plugin for latest r2kons 2025-05-11 11:26:53 +02:00
pancake 713183d2c7 Rebrand baby into yolo 2025-05-11 11:22:47 +02:00
pancake 1f5406ae55 Implement baby mode and show colorful feedback in decai auto 2025-05-09 11:16:33 +02:00
pancake a9e5aa114b Improve auto mode for local models 2025-05-09 11:02:00 +02:00
pancake 67c1a6d129 Improve auto mode in decai 2025-05-09 00:49:40 +02:00
pancake 91bbd87c2d Emulate reasoning with non-think models via prompt engineering 2025-05-08 22:35:54 +02:00
pancake e1f8ddbff3 Add decai -e think=0,1,2 to support and filter out the think tag 2025-05-08 21:27:40 +02:00
pancake 562aeb1ea5 Fix help for decai -r 2025-05-08 18:58:44 +02:00
pancake 2444f12acc Baby mode for decai auto 2025-05-07 23:02:49 +02:00
pancake d0d58670ec Trim markdown blocks for decai auto 2025-05-07 22:45:35 +02:00
pancake 5eb8231ee1 Fix r2ai help 2025-05-07 22:32:35 +02:00
pancake 10775172d8 Install deps before linting 2025-04-30 17:42:25 +02:00
pancake 5c39cc4c3f Dont check for r2 in the root makefile and refactoring the linting 2025-04-30 11:31:44 +02:00
pancake 79ac83ff47 Missing cdpy for linting 2025-04-29 20:33:47 +02:00
pancake 6869d9e259 untab 2025-04-29 00:03:17 +02:00
pancake dba793411f Update the CI 2025-04-28 11:16:44 +02:00
pancake 4f095b5e90 Refactor old Python file into py/ and refresh the readmes 2025-04-28 11:06:17 +02:00
pancake b4b73d3ceb aout leftover 2025-04-26 18:08:16 +02:00
pancake c818e170a0 The root makefile just informs, no longer doing the python thing 2025-04-26 12:25:52 +02:00
Pau RE 88955740d7 Add system install for r2ai-plugin 2025-04-26 10:02:51 +02:00
pancake 1859d64ca7 Fix r2ai pkgname in the README 2025-04-23 20:05:59 +02:00
pancake 5af7f44498 Deprecate r2ai.api_key 2025-04-22 07:31:49 +02:00
pancake 0fb9ce8e8c Improve r2ai -e model= output 2025-04-21 23:33:34 +02:00
pancake 2d444e7159 deno fmt decai 2025-04-18 11:33:57 +02:00
pancake 6b2e327fc3 Honor dir.tmp in decai 2025-04-18 11:33:25 +02:00
pancake 6276fe6685 Add vdb implementation in Swift 2025-04-16 17:37:58 +02:00
pancake 3c67d6693f rename apiproxy 2025-04-16 17:28:01 +02:00
pancake 35a2139380 Implement an openapi proxy with vdb integration 2025-04-16 17:27:28 +02:00
pancake 594e2f4f20 Fix indent 2025-04-15 16:25:25 +02:00
emilius3m 5f281591b4 Clean model name 2025-04-15 16:07:28 +02:00
cryptax 829ffa0133 indentation fix on r2ai.c 2025-04-15 16:06:07 +02:00
cryptax 2c1b41ad57 fixing default system prompt, decreasing default max_runs 2025-04-15 16:06:07 +02:00
cryptax 91ede7cc72 fixing the programming language bug 2025-04-15 16:06:07 +02:00
cryptax 4b03d2458c adding possible model names for groq and mistral 2025-04-15 16:06:07 +02:00
pancake 57ab90b26a Add more quotes for vdb and improve the current ones 2025-04-15 13:28:59 +02:00
pancake eba9d2ca4b Fix groq url 2025-04-10 13:30:01 +02:00
pancake 165cd2bc46 Add support for groq to decai 2025-04-10 13:21:54 +02:00
Daniel Nakov c4f1ae900f Add support for reasoning_content (#158) 2025-04-09 22:07:29 -04:00
Daniel Nakov e090521bd2 Fix non-auto opts like -x -d etc (#157)
Fixes https://github.com/radareorg/r2ai/issues/155
2025-04-07 14:12:46 -04:00
pancake 094fc325c5 Release 0.9.4 2025-04-07 12:02:15 +02:00
daniel nakov b557e60b8f Fix api key checking. -e r2ai.api_key > env > .r2ai-key file 2025-04-06 18:51:50 +02:00
daniel nakov 97e62cde53 Remove adding additional empty user message 2025-04-06 18:51:50 +02:00
pancake 5b8ee5201e Handle r2ai -e model=? 2025-04-06 10:58:53 +02:00
pancake fb70b8ad32 Remove .bak file 2025-04-06 10:50:12 +02:00
pancake 67d1528895 Add C indent for r2ai 2025-04-06 10:38:33 +02:00
daniel nakov c2aba8ce86 Add support for anthropic thinking 2025-04-06 09:08:19 +02:00
pancake c5c0e13b64 Handle ? in -e r2ai.api 2025-04-02 17:37:17 +02:00
pancake 6fa0ed3d8e Add TypeScript implementation of the vectordb 2025-04-02 11:37:45 +02:00
pancake 8fdb499906 Do not log vector context in chat log to save tokens 2025-04-02 11:27:59 +02:00
pancake 24b958d040 Bring back the vector database to r2ai.c 2025-04-02 11:16:52 +02:00
pancake 1c9a9973da Fix loading API keys from file and environment 2025-04-02 09:24:12 +02:00
pancake e5dea8baa4 Honor user's message provided to r2ai.input 2025-04-02 09:12:44 +02:00
pancake 739da0859c Initial support for ollama based embeds 2025-04-02 08:57:11 +02:00
pancake 3fdfa351bf Indent markdown.c 2025-04-02 08:49:28 +02:00
pancake 6b04f5f691 Remove markdown code blocks in decai's output 2025-04-02 02:24:09 +02:00
Daniel Nakov 5b246273f2 Error handling with retry; Fix some known differences between providers; Add some more -e; Fix api key looking (#149) 2025-03-31 23:24:20 -04:00
daniel nakov 2c5634073f Add ask_to_execute support; Add mistral and grok as providers 2025-03-31 21:02:54 +02:00
Daniel Nakov d8f483e361 Add http retry with expo backoff (#147) 2025-03-31 09:51:24 -04:00
Daniel Nakov e7b154ab13 Use R2_CURL; Add javascript tool; Add Markdown renderer (#146)
* Use R2_CURL

* Add javascript tool; Add markdown rendering

* R_LOG_INFO->R_LOG_DEBUG

* Fix output formatting
2025-03-31 00:07:11 -04:00
Daniel Nakov e1f90c3c92 Use libcurl if available (#145)
* Use libcurl if available

* Fix socket version compat; Better tool handling and output
2025-03-30 11:00:22 -04:00
daniel nakov ee75d5018a Implement chat log; Add tokens to stats 2025-03-30 08:11:39 +02:00
Daniel Nakov 808b3581db Add stats display; Support for all the other providers (#143)
* Add stats display; Support for all the other providers

* Strip ANSI for LLM
2025-03-29 17:42:02 -04:00
Daniel Nakov c39c0a0757 Add .clang-format; print tool calls and responses (#142) 2025-03-29 13:12:53 -04:00
daniel nakov 042acc1be0 Auto mode, rewrite everything 2025-03-29 09:45:52 +01:00
pancake 9a12668822 Add support for openwebui to Decai 2025-03-28 16:59:58 +01:00
Steven England b943ec8be1 fix: use a normal input() statement to be cross os compliant (#135) 2025-03-25 20:21:18 +01:00
cryptax 65371a6967 Detect markdown in decai auto for proper json parsing 2025-03-25 20:20:36 +01:00
pancake 3fd244f7f0 env -> envp 2025-03-25 18:12:29 +01:00
pancake 46d55b3167 Fix #133 - Add system prompt option for Decai 2025-03-25 18:11:58 +01:00
pancake 1bf249188d Do not truncate Gemini's queries 2025-03-25 17:35:09 +01:00
pancake 38aeec0454 mv wip attic 2025-03-25 17:22:39 +01:00
pancake 7ad7bf27ed Recommend hhao/qwen2.5-coder-tools for decai 2025-03-14 10:40:53 +01:00
pancake 647bd799f4 Fix user-install for decai 2025-03-13 17:45:04 +01:00
pancake 9b5b8d2ae6 Enable the decaiDeterministic option by default 2025-03-11 19:10:40 +01:00
pancake b9a1066556 Add model listing for the gemini api in decai 2025-03-11 19:10:20 +01:00
pancake 3d7af1d8d9 Support deterministic output for Gemini 2025-03-11 19:05:55 +01:00
pancake f591d8e03f Fix decai -m 2025-03-11 18:42:51 +01:00
pancake 1268ab1567 Better prompt and decompiler rules for Decai, Swift works even with Mistral now 2025-03-11 18:38:46 +01:00
pancake fa2af78c15 Use more curlPost and improve prompt structure for better results in Decai 2025-03-11 16:29:56 +01:00
pancake 32a16dff4d Fix decai -e with empty value 2025-03-11 14:56:23 +01:00
pancake 5e8798be91 Unify the Decai language logic 2025-03-11 13:52:29 +01:00
pancake 1ae7a2977d Fix dupped instructions in decai query 2025-03-10 19:04:55 +01:00
pancake c4deedbd17 Handle ollama error messages 2025-03-10 18:10:21 +01:00
pancake 6cd8b99594 Fix arch name for debian packages (aarch64 is now arm64) 2025-03-05 21:48:09 +01:00
pancake 7ceb4147f8 Refactor decai -e config logic 2025-03-05 21:05:21 +01:00
pancake 155456912a Dont install files in r2ai-c make all 2025-03-05 20:09:36 +01:00
pancake ca8c0f3c07 Fix warning for r2-5.9.8 2025-03-05 19:44:58 +01:00
pancake 43953507cc Add debian package for r2ai-c 2025-03-05 18:04:34 +01:00
pancake edce63ea67 Add debian package for decai 2025-03-05 17:59:44 +01:00
pancake aac6092151 Improve decai -H help message 2025-03-01 20:37:22 +01:00
pancake 39ddddbc7e Add decai -k and initial refactoring to reuse logic patterns 2025-03-01 05:08:37 +01:00
pancake 3b9a325e3b Fix uaf and dblfri in r2ai.c 2025-02-27 17:16:05 +01:00
pancake ef975edb9d Update to claude 3.7 in r2ai.c 2025-02-26 17:11:33 +01:00
cryptax 060a897a09 adding example for ollama in decai 2025-02-25 22:10:41 +01:00
cryptax b687f76566 adding mention of claude 3.7 + how to use ollama models 2025-02-25 22:10:41 +01:00
daniel nakov 2c747e42d1 Fix #128 2025-02-25 22:10:25 +01:00
pancake e48a0d1166 Implement -m? in r2ai.py to suggest some model uris 2025-02-25 16:26:03 +01:00
pancake 5d16354f56 Always drop params for mistral too 2025-02-25 16:25:53 +01:00
pancake 5f577c5f7d Load mistral key from ~/.r2ai.mistral-key in r2ai.py 2025-02-25 16:25:23 +01:00
pancake 07ee07835d Fix ollama support for r2ai.py 2025-02-25 13:46:15 +01:00
pancake c6e3b5149a Use Claude's 3.7 sonnet instead of the 3.5 one by default 2025-02-25 13:28:49 +01:00
pancake 29aa5d8e60 Add 'decai -m' as an alias for 'decai -e model=' 2025-02-25 13:28:33 +01:00
pancake 6e9cb22f2f List models for claude, openai, ollama and mistral via 'decai -e model=?' 2025-02-25 13:28:06 +01:00
pancake f7497c653b Release 0.9.2 2025-02-19 18:30:52 +01:00
pancake cd3474dd06 Bump to the latest llama-cpp-py 2025-02-19 18:29:58 +01:00
daniel nakov 10ecbba3af Fix #122 and #124 2025-02-18 08:04:08 +01:00
cryptax 406d5b2204 documentation and help for using mistral api keys 2025-02-15 11:09:19 +01:00
pancake ea379de711 Add missing -a in decai's help 2025-02-13 13:42:18 +01:00
pancake 2ee71ec281 Cant get deterministic mode to work with Mistral 2025-02-11 01:08:37 +01:00
pancake f3e981f895 Initial support for mistral.ai to decai 2025-02-11 00:53:39 +01:00
pancake 9adc3f2c6f Support deterministic mode for openai and claude 2025-02-10 23:22:33 +01:00
pancake baefc5ae51 Implement decai -e deterministic=true for ollama 2025-02-10 23:07:42 +01:00
pancake 14181ec917 Initial support for custom decompilation pipelines in decai 2025-02-10 19:20:54 +01:00
pancake 825c3006ec Use 16 dimension and global token frequency to r2ai.c-vdb 2025-02-09 22:31:31 +01:00
Daniel Nakov c9476904bb Add ollama to r2ai.py 2025-02-06 19:48:47 +01:00
pancake e32487e244 Updated vector database implementation in r2ai.c 2025-02-03 16:46:47 +01:00
pancake 4055d13838 Add initial VLLM support to decai 2025-02-03 16:46:44 +01:00
daniel nakov 95d6554bf7 Fix for o3-mini 2025-02-01 10:04:10 +01:00
cryptax 7a8511d29b typo in the doc make install-server installs the r2ai-server 2025-01-30 09:31:04 +01:00
cryptax a1ff327d22 modified doc for r2ai auto review prompt 2025-01-30 09:31:04 +01:00
cryptax 4329a12a8e editing command execution 2025-01-30 09:31:04 +01:00
pancake f663710c0d Add README for the C plugin 2025-01-29 19:50:39 +01:00
pancake 9cb9c8b16c Add README for decai 2025-01-29 19:35:48 +01:00
pancake 3ea6d0d804 Initial implementation of the rag reasoning algorithm for r2ai.c 2025-01-29 11:44:41 +01:00
pancake 641d090c08 Handle r2ai? and add -r for repl 2025-01-28 23:53:17 +01:00
pancake f3c1e2df45 Show warning when not finding data in the vdb refresh 2025-01-28 23:51:04 +01:00
pancake 8c7fd96436 Fix typo in decai prompt 2025-01-28 23:35:07 +01:00
pancake bcc70c0103 Add deepseek support to ai.sh 2025-01-28 15:38:24 +01:00
cryptax 782ed359ef add doc on auto mode approval + API key table + better explanation on running r2ai 2025-01-27 23:26:04 +01:00
cryptax b00b7b52af adding setting ask_to_execute 2025-01-27 23:26:04 +01:00
pancake 7a40faaf8b Add llama-vim.sh 2025-01-24 19:30:52 +01:00
pancake e572cdb3d1 Unify the input reading for ai.sh 2025-01-24 17:18:20 +01:00
Axelle d182f3e3e9 Implementing max input tokens to be sent to AI as optional setting 2025-01-23 15:08:59 +01:00
Axelle 6c45fbc8da Option -D does not exist. It is -dD now. 2025-01-23 11:33:50 +01:00
Axelle 286de473f1 Adding -R option to usage() 2025-01-23 11:33:50 +01:00
pancake 005e6d1d88 Add -h and -- help message for ai.sh 2025-01-22 11:25:15 +01:00
pancake 197bb7bab5 Use ollama by default in decai 2025-01-21 19:13:17 +01:00
pancake d9b697d04c Support ollama in decai 2025-01-21 17:49:32 +01:00
pancake 2e753cafa6 Support system prompts in non-rawdog mode 2025-01-20 18:26:33 +01:00
daniel nakov b5383f5b87 Python: auto fixes; better cost display 2025-01-16 22:29:42 +01:00
daniel nakov 78ef1750de Python: Fix non-streaming; support o1; show cost; set chat.auto_max_runs; 2025-01-16 15:54:27 +01:00
pancake 565120628f Implement r2ai.c -i to query file contents 2025-01-08 01:36:44 +01:00
pancake 1592d4281f Implement r2ai.c -s to resolve function signature 2025-01-08 01:32:32 +01:00
pancake ec6d35c390 Implement -V/-Vr in r2ai.c 2025-01-08 01:23:36 +01:00
pancake e55027f49e Port decai's auto mode into r2ai.c 2025-01-08 01:14:49 +01:00
pancake 93544c9eaa More quotes 2025-01-07 21:06:54 +01:00
cryptax 68c2868674 Adding Pico le Croco as r2clippy alternative 2025-01-07 17:46:38 +01:00
pancake fb5fc88d97 Add missing header 2025-01-02 02:55:02 +01:00
pancake 235761b01a Integrate vdb with r2ai.c for embeddings 2025-01-02 02:16:12 +01:00
pancake d00d56b47d Initial implementation of the vector database for r2ai.c 2025-01-02 02:00:49 +01:00
pancake 83b22d07f6 Add support for deepseek in decai 2025-01-01 18:02:57 +01:00
pancake 223a266035 Add ollama to ai.sh 2024-12-27 23:39:06 +01:00
pancake 18f7908af4 Suggest codegeex4 for r2ai.c 2024-12-27 23:27:17 +01:00
pancake dece4a9bdb Implement r2ai -v for variable names and types in r2ai.c 2024-12-27 23:26:08 +01:00
pancake 410f084449 Implement r2ai.c -n 2024-12-27 23:17:56 +01:00
pancake bb496c1d9e Improve decompiler prompt for r2ai.c 2024-12-27 23:13:26 +01:00
pancake f9d8965609 Add system prompt for openai 2024-12-27 21:39:15 +01:00
pancake e3493c66d6 Warn when trying to run r2ai from r2ai 2024-12-27 00:29:07 +01:00
pancake 2b9a54fdb2 Handle system prompt for xai 2024-12-26 18:36:31 +01:00
pancake f24e945a84 Add support for XAI 2024-12-26 18:34:41 +01:00
pancake c500ab83c4 Implement -dr in r2ai.c 2024-12-26 17:59:38 +01:00
pancake e3ca8c966d Add system prompt, default with ollama:qwen 2024-12-26 17:30:31 +01:00
pancake 52df442873 Initial support for ollama 2024-12-26 16:43:24 +01:00
pancake eacd566522 Fix build on r2-5.9.8 (non-git) 2024-12-26 00:29:05 +01:00
pancake c946298245 Update CI stuff and also build the C plugin 2024-12-26 00:29:05 +01:00
pancake 9334046f33 Add uninstall target for r2ai-native 2024-12-25 23:33:52 +01:00
pancake 4dab688f11 Fix r2ai.c build on linux 2024-12-25 20:12:39 +01:00
pancake ee1881fcd9 Add missing header 2024-12-25 20:11:27 +01:00
pancake b19934c1f2 Remove r2ai.r2 alias script 2024-12-23 16:25:12 +01:00
pancake 8ba25bae69 Upgrade llamacpp from 032 to 035 2024-12-23 16:24:09 +01:00
pancake 81188c663b Initial (-d)ecompilation for the C rewrite 2024-12-19 20:28:40 +01:00
pancake ad2ac6bf5e Fix assert with -w#notanumber 2024-12-19 20:11:08 +01:00
pancake 56af96ee79 Fix decai -q 2024-12-19 20:07:32 +01:00
pancake 01cfec0a43 Fix #105 - broken shell escape 2024-12-17 22:38:42 +01:00
pancake 4506610075 Build it as separate objects 2024-12-17 18:10:45 +01:00
daniel nakov cf8331056f Split provider implementations in separate files; Add Gemini 2024-12-17 18:04:44 +01:00
cryptax 43042dca9c Improve README and add usage documentation 2024-12-17 18:04:18 +01:00
pancake 6485e67501 Fix non-stream openai 2024-12-12 17:38:05 +01:00
pancake cf5600ed88 More config fixes and better defaults for model/provider selector 2024-12-12 16:56:48 +01:00
pancake 31dc32fe12 Setup config vars in the init/fini callbacks 2024-12-12 16:42:09 +01:00
daniel nakov e838b7028c Add anthropic; Implement streaming for openai and anthropic 2024-12-12 16:11:04 +01:00
pancake 521025ce76 Add decai -R to reset the prompt 2024-12-12 14:32:58 +01:00
daniel nakov ea71d598ca Fix for new models not yet added to litellm 2024-12-11 23:30:42 +01:00
pancake 5fde78def8 Add support for X.AI to decai 2024-12-11 14:11:22 +01:00
pancake 8e1913a873 Implement function calling in decai via '-a' 2024-12-11 13:59:33 +01:00
pancake 799caf392f Use gpt-4-turbo which have 128K token context instead of the 8K of the gpt4 2024-12-11 12:55:03 +01:00
pancake 0c19f7334c Refactor native/client into src/ 2024-12-11 12:36:02 +01:00
pancake e4de4b30b2 Support local llama server and set config variables in r2ai.native 2024-12-11 12:34:42 +01:00
pancake 6aa5062d4d Initial client plugin working with openai 2024-12-11 11:57:34 +01:00
pancake f330f46888 Release 0.9.0 2024-12-11 04:07:23 +01:00
Pau RE fe2ea28c7e Add GH Actions to create releases automatically 2024-12-10 21:15:50 +01:00
pancake a8017b68fa Implement decai -dr to recursive decompile and query functions 2024-12-06 15:00:36 +01:00
Daniel Nakov 936e0f96ab Make r2ai-plugin work r2pm 2024-12-01 17:34:59 +01:00
pancake 37d3d6f383 Support Gemini in decai 2024-12-01 11:35:13 +01:00
pancake 5f07eb0566 Support Gemini in SHAI and some fixes for r2ai/gemini but still broken 2024-12-01 11:27:19 +01:00
pancake f877ebb373 Support Claude and OpenAPI in 'shai' 2024-12-01 10:37:23 +01:00
pancake c00c955431 Add shellscript version of 'openai' client 2024-11-30 11:34:02 +01:00
daniel nakov 00a6e7185e Update litellm 2024-11-27 00:20:34 +01:00
pancake faee05108a Use the latest llama 3.2 2024-11-24 21:13:40 +01:00
pancake 0a441a1d41 Support o1 models in decai 2024-11-21 17:11:07 +01:00
pancake 807b37d6cf Fix indentation 2024-11-21 12:38:58 +01:00
pancake 5ae84a589d Implement listing models on remote openapi instances 2024-11-21 10:44:17 +01:00
pancake c1876aa8a9 Add api/generate endpoint under the openapi2 2024-11-21 10:27:38 +01:00
pancake be575e6932 Release 0.8.8 - after r2con2024 2024-11-19 13:24:49 +01:00
daniel nakov 39c0c921d9 Fix auto rawdog mode for qwen-coder 2024-11-16 01:48:29 +01:00
Daniel Nakov c1fa76e47e Use lazy imports to speed up initial loading time 2024-11-11 23:45:28 +01:00
Daniel Nakov fa42783c11 Fix not getting python output when running as plugin; Fix litellm connection reset on openai calls; switch run_python and execute_binary to just use python subprocess, seems to mess up r2pipe 2024-11-09 20:08:46 +01:00
Daniel Nakov 59f891fd19 Add new execute_binary command for auto; some UI bug fixes 2024-11-07 22:05:06 +01:00
daniel nakov ce5b851a18 fix some auto streaming bugs 2024-11-07 19:46:07 +01:00
daniel nakov 096987e24b Fix VV chat taking over main thread 2024-11-02 14:46:45 +01:00
daniel nakov 660ca3cdd1 Fix VV for local models 2024-11-01 23:59:17 +01:00
pancake 1dc00ff4df Use symlinked path for -mm 2024-10-30 05:15:03 +01:00
pancake eba5741112 Remove nick from logo 2024-10-29 04:48:02 +01:00
pancake 2a8c7dad0b Implement decai -s to display the function prompt assuming its types and arguments 2024-10-28 23:06:01 +01:00
daniel nakov 91ac2fbeff Auto updates to handle more params and non-streaming mode 2024-10-28 19:36:34 +01:00
daniel nakov 69089ae66c Auto mode - rawdog local models; add tool support for llama3.1, 3.2, qwen, functionary 3.1 and 3.2 2024-10-28 04:33:02 +01:00
daniel nakov d26f280943 Make llama verbose respond to logger settings; Add the functionary tokenizer 2024-10-28 04:32:42 +01:00
pancake fba8823a32 Fix input with GNU readline, ref to #79 2024-10-24 12:42:19 +02:00
pancake b15a4d8435 Fix warning importing transformers without pytorch
* "None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used"
2024-10-24 12:33:38 +02:00
Adam Bromiley bd8f2ac002 Use portable script sourcing 2024-10-24 11:37:41 +02:00
pancake bb3e255746 Improve decai prompt, tested with OpenAI, Qwen and Claude 2024-10-23 10:42:23 +02:00
pancake 0f3dfdf54d Rename chat.use_completion into chat.rawdog 2024-10-23 10:10:45 +02:00
pancake cafbbf98c1 Update list of recommended models 2024-10-23 10:09:27 +02:00
pancake 7e4474863a Add support for Astraios models and improve granite terminator token 2024-10-23 10:08:54 +02:00
pancake 1aee10520f Fix #77 - hf_hub_download warnings 2024-10-23 01:56:49 +02:00
pancake 306c3ba060 Reset the llama instance when changing the model 2024-10-22 23:51:30 +02:00
daniel nakov 9411f922cb Update claude sonnet version 2024-10-22 23:01:06 +02:00
pancake 6eeaef4715 Honor system prompt in the non-chat-completion mode 2024-10-22 19:20:53 +02:00
pancake 0bc0e0af1c Fix make venv creation 2024-10-22 18:58:43 +02:00
daniel nakov 9d83ec3754 Use chat completions for llama.cpp 2024-10-22 15:29:51 +02:00
daniel nakov c6987e5a90 Fix llama in auto 2024-10-22 15:28:29 +02:00
daniel nakov 94820d1197 Add .tcss files to package 2024-10-22 07:54:50 +02:00
daniel nakov b7b3756fb3 README updates 2024-10-22 02:19:32 +02:00
daniel nakov e9798cbc7b Makefile fixes 2024-10-22 02:16:59 +02:00
pancake cd647e54bb Improve decai prompt 2024-10-22 00:58:37 +02:00
pancake f576d46420 Better decai prompt and use r2.syscmd to curl 2024-10-22 00:17:27 +02:00
pancake 9011f08d91 Support local models in is_litellm_model() 2024-10-21 23:57:18 +02:00
Daniel Nakov 75c9b85439 Rewrite auto to use litellm; add spinner (#69) 2024-10-21 23:56:52 +02:00
daniel nakov 2462ffbaa2 UI fixes and cleanup 2024-10-21 22:42:28 +02:00
daniel nakov f0f8aee4e0 Make sure r2lang and r2 are in sync 2024-10-21 22:42:00 +02:00
pancake 9b48224ea8 Fix windows installation instructions 2024-10-21 21:09:14 +02:00
pancake 7733d77a80 Dont use the missing requirements anymore 2024-10-21 21:08:13 +02:00
pancake 63c2024344 Fix "duplicated start_of_text" warning when using llama models 2024-10-21 21:05:42 +02:00
daniel nakov 766318db04 Use litellm for remote models in chat mode 2024-10-21 15:29:01 +02:00
Daniel Nakov 1b8d17fa77 Packaging redo 2024-10-21 11:12:02 +02:00
pancake 5913a622e2 Add SocialAI-like implementation in plain llama-py 2024-10-19 18:40:47 +02:00
pancake d074d75879 Release 0.8.6 in sync with r2-5.9.6 2024-10-14 00:58:41 +02:00
pancake c6a806312a Fix decai cache 2024-10-13 22:06:18 +02:00
pancake 94b4059329 Support translations in decai -x 2024-10-13 18:37:08 +02:00
pancake e4d2f77658 Fix decai -x 2024-10-13 17:49:13 +02:00
pancake 3db25afffa Implement decai -e model= 2024-10-10 13:22:34 +02:00
pancake 39f23391eb Improved decai -H verbose help 2024-10-10 13:11:28 +02:00
pancake a51968b5e2 Update llama-cpp-python dependency 2024-10-10 12:54:07 +02:00
pancake 06e68e14fb Fix decai's curl call glitching with local say when failing 2024-10-09 12:51:16 +02:00
pancake c695fb1975 Fix decai cache glitch 2024-10-09 12:50:32 +02:00
pancake dc022d7329 Improve decai's default prompt 2024-10-09 12:50:21 +02:00
pancake cd42a61b40 Implement decai -e cache using r2 'ano'tations 2024-10-09 08:50:48 +02:00
pancake cebab343fc Add decai -D and make decai -d handle multiple function decompilation in a shot 2024-10-04 20:38:42 +02:00
pancake d5387d0f14 Add the decai -i command 2024-10-04 11:00:15 +02:00
pancake 4077edd401 Alias huggingface as "hf" 2024-10-02 14:56:59 +02:00
Roman Valls Guimera d70258be54 Add support for HuggingFace 🤗 inference API 2024-10-02 14:53:11 +02:00
pancake 7909bb2acd Fix -m- 2024-10-02 02:11:33 +02:00
pancake f6626aa9b5 Implement ! and !# for listing and re-running history lines 2024-10-02 02:01:23 +02:00
pancake 9fc93cd876 List untracked models from disk 2024-10-02 01:23:59 +02:00
pancake efb169d775 Fix le claude 2024-10-02 00:57:59 +02:00
Marcel Alexandru Nitan bccc7748c3 Add filter_print to support filtering prints 2024-09-29 16:12:26 +02:00
Marcel Alexandru Nitan bb80d4caf5 Support setting callbacks on env change; Move debug level set in env 2024-09-29 13:31:48 +02:00
pancake 71b1b26c72 Add podcast.sh script 2024-09-28 22:58:30 +02:00
daniel nakov 25507a0619 TUI: render markdown at the end only, due to perf 2024-09-28 01:31:15 +02:00
daniel nakov 2c93e89b94 TUI: switch to using r2cmd {; get filename if binary already opened 2024-09-28 01:31:15 +02:00
daniel nakov dafd93db9f TUI: Ask for API KEY if one is not set in env 2024-09-27 00:11:44 +02:00
pancake bc78ba62ce Add venv to the pypath 2024-09-26 21:14:38 +02:00
pancake 3f31a03aac Disable lint in the main 2024-09-25 11:01:15 +02:00
Daniel Nakov e5445fd492 TUI updates and some error handling
* ctrl+o to open binary, ctrl+m for model selector, error handling, some formatting updates
2024-09-25 09:34:44 +02:00
pancake 5acab2ab05 Fix pythonpath for r2ai-plugin 2024-09-24 18:18:29 +02:00
pancake 4b15e0e743 Fix #57 - Make more stuff optional to load it as an r2 plugin again 2024-09-23 17:51:46 +02:00
daniel nakov 53713b79d4 Fix TUI tool calls formatting 2024-09-19 21:58:05 +02:00
daniel nakov 56c35ef19e Fix TUI Chat messages formatting 2024-09-19 07:36:21 +02:00
daniel nakov eda9dac0b6 Fix TUI File Browser and chat bug 2024-09-19 00:57:37 +02:00
pancake 15679d9c79 Dont loose the prompt when failing to start the webserver 2024-09-17 19:13:19 +02:00
pancake 5123135da9 Only log shutting down webserver when running in ^C 2024-09-17 12:35:20 +02:00
pancake 275f2a5e8a Add llm.layers to make it configurable 2024-09-17 12:21:40 +02:00
pancake 599a9e79e6 Disable progressbar for now 2024-09-17 11:02:41 +02:00
pancake 67212d7131 Fix pylint complains from main.py 2024-09-17 11:02:38 +02:00
pancake b942d67faf Delete decai -M leftover 2024-09-17 02:44:37 +02:00
pancake d133755a8d Lintfix: Dont else after return 2024-09-16 20:08:54 +02:00
pancake 34bfb70045 Use the new test command in decai to avoid catting unexistent files 2024-09-16 19:17:41 +02:00
pancake 2b3cf4273e Support realpath in r2ai.sh 2024-09-16 11:11:36 +02:00
pancake d58e9a9f77 Deprecate -t, we have -e llm.temperature already 2024-09-16 11:08:57 +02:00
pancake 768c7ffc26 Handle -p -e and -m in the r2ai.py 2024-09-16 11:08:31 +02:00
pancake f12dd71e3c Fix passing -m to r2ai via r2ai-server 2024-09-16 10:50:34 +02:00
pancake c2b842b51d Fix #41 - Add more functionary models in the -M suggestions 2024-09-16 00:01:46 +02:00
pancake 4460c980b8 Remove the Using GPU/CPU noisy message 2024-09-12 15:21:08 +02:00
pancake 97e7781e74 Fix r2ai -w 2024-09-12 10:17:16 +02:00
daniel nakov 9237600923 TUI [draft] 2024-09-11 22:26:00 +02:00
Marcel Alexandru Nitan 1e21d46314 Setup progress handlers with rich Progress 2024-09-11 06:49:41 +02:00
Marcel Alexandru Nitan 7d033b334e Remove repeat_penalty for openai based ais 2024-09-10 18:45:31 +02:00
pancake 18e6cda31f Fix invalid format string in -w 2024-09-10 17:10:23 +02:00
Marcel Alexandru Nitan 7dff3396ff Make sure we close the server properly when handling KeyboardInterrupt 2024-09-10 17:08:08 +02:00
pancake d840e9f704 Add llm.repeat_penalty option 2024-09-10 17:07:59 +02:00
pancake df09f8bbb8 Add -mm and -m- to list and delete downloaded models 2024-09-10 11:21:14 +02:00
pancake 9d8f1e3575 Recreate llama instance in -m multiple times when changing between models 2024-09-10 10:56:03 +02:00
Marcel Alexandru Nitan 9f2f270388 Fix a bug introduced in the last 2 commits
The code isn't confusing at all xD
2024-09-09 20:27:52 +02:00
Marcel Alexandru Nitan 5287453b9d Use python's builtin logging instead of dirrectly printing 2024-09-09 20:17:23 +02:00
pancake afbca14b35 Add missing license file 2024-09-09 16:46:28 +02:00
Marcel Alexandru Nitan 190f166667 Use the requests library for openai/openapi only when necesary 2024-09-09 15:19:13 +02:00
Marcel Alexandru Nitan cf62e4bda8 Allow passing model when using openapi 2024-09-09 13:07:08 +02:00
pancake a343a8347e Remove -m and -M from decai to avoid confused users 2024-09-09 08:53:26 +02:00
pancake 96a68337f2 Clarify some stuff in the readme 2024-09-08 22:06:53 +02:00
pancake 7b2171d969 Add support for context files in decai 2024-09-08 21:32:28 +02:00
pancake 8b7d639692 Suggest smaller granite model for decompilation 2024-09-08 21:10:39 +02:00
pancake 1420fdddf1 Fix -l 2024-09-08 17:48:43 +02:00
pancake 01d7bc35f4 Suggest another model for decai 2024-09-08 16:48:28 +02:00
pancake ed862cd961 Fix python 2024-09-08 14:05:44 +02:00
pancake 9d5be28f4e Recommend granite 2024-09-08 14:02:20 +02:00
pancake 9d3af3fd07 Make r2ai -w work again 2024-09-08 14:01:37 +02:00
pancake 7d5a85b95a Better prompt for decompilation with local models 2024-09-08 13:55:48 +02:00
pancake 875f819dbc Support granite template for the ibm model 2024-09-08 12:04:52 +02:00
pancake 75259b1f48 Support abspaths in r2ai-server for models 2024-09-08 11:52:10 +02:00
pancake 4f642d5eb0 Release 0.8.4 2024-09-06 20:35:27 +02:00
pancake 8df5b36115 Use r2.syscmds instead of r2.cmd("!") in decai 2024-09-06 20:34:23 +02:00
pancake 44b2102c4a Use -c= for r2ai on r2ai-server 2024-08-31 15:08:28 +02:00
pancake 47c8e76f33 r2ai takes - flags with _ and honor that in r2ai-server 2024-08-30 23:39:22 +02:00
pancake 62b1d18a85 Fix decai -n output with address and command only 2024-08-30 13:20:58 +02:00
pancake 2ebaecafc7 Fix lint complain 2024-08-30 01:03:27 +02:00
pancake 5874bc1795 Fix r2ai backend for decai and improve prompt for local decompilation 2024-08-29 22:27:15 +02:00
pancake f2fb5379a7 Updated models, some old ones fall into infinite loops with latest llamacpp 2024-08-29 21:49:58 +02:00
pancake a3818d698f Fix local models and update llama-cpp 2024-08-29 21:03:05 +02:00
pancake 2fe81d355b Fix r2ai-server for r2ai 2024-08-29 20:26:56 +02:00
pancake 3aef695688 Handle r2ai -c 2024-08-29 20:26:14 +02:00
pancake 69185cd775 Fix r2pm -r r2ai issue 2024-08-29 20:20:19 +02:00
pancake 87a10d5892 chmod +x 2024-08-29 20:16:27 +02:00
pancake ace2457855 Fix r2ai-server script 2024-08-29 20:15:12 +02:00
pancake ad8ae2f576 Fix symlink installations 2024-08-29 20:06:31 +02:00
pancake 668101edee r2plugin -> decai to avoid confussions 2024-08-29 20:02:14 +02:00
pancake 736b40a83b Add missing colorama dependency 2024-08-29 16:48:36 +02:00
Fernando Domínguez bb7170a9d7 Fix context hints 2024-08-29 16:48:36 +02:00
Fernando Domínguez 796792cf16 Break down auto chat function into smaller handlers to make it more readable 2024-08-29 16:48:36 +02:00
Fernando Domínguez 078edd8eed Add support for bedrock and clean the code 2024-08-29 16:48:36 +02:00
pancake 765fea6ae1 Move r2ai-server into its separate directory 2024-08-26 21:13:55 +02:00
pancake f584737b9c Ignore other errors when reading history file 2024-08-26 09:04:07 +02:00
pancake 2e2d809b43 Add decai -q,-Q fix -v anx -v and better outputs 2024-08-23 18:44:35 +02:00
pancake a87c2006df Escaping chars and strip colors 2024-08-23 11:14:16 +02:00
pancake cbb5b4f25e Also from pyproj 2024-08-22 15:02:48 +02:00
pancake 445c30a8c0 pytorch doesnt seems to be mandatory dep 2024-08-22 15:02:25 +02:00
Pau RE 0015800768 Add r2plugin system install 2024-08-22 12:12:31 +02:00
pancake eef769eb94 decai now requires r2 from git to work for openai/claude 2024-08-21 19:49:48 +02:00
pancake 1a440d9c9f Fix fileDump 2024-08-21 17:52:34 +02:00
pancake 83a4049a64 Refactor to reduce dependency on filesystem in decai 2024-08-21 16:10:37 +02:00
pancake 3ea744bdbe Support OpenAI in decai natively 2024-08-20 17:55:22 +02:00
pancake 83e92bec0f Add native support for anthropic in decai 2024-08-20 17:19:24 +02:00
pancake 12ffc0ca58 Fix decai prompt for claude 2024-08-20 14:12:28 +02:00
pancake b0a642e223 Better prompt for decai 2024-08-20 14:01:57 +02:00
pancake 007fc41451 Improvements for r2ai-server and make decai happy without r2ai-w 2024-08-20 13:39:22 +02:00
pancake bd41c318f5 More documentation for decai setup 2024-08-19 19:12:20 +02:00
pancake 5713582d42 Better prompt for decai 2024-08-19 19:02:43 +02:00
pancake bcecc0e76b Better names for the decai config variables 2024-08-19 18:58:16 +02:00
pancake 7fa2f4e72a Add decai -H and decai -e decai.debug 2024-08-19 18:50:12 +02:00
pancake 8f8f880637 Fix syntax error in decai 2024-08-19 18:29:11 +02:00
pancake 4f7a7f4f0d Bump llama-cpp-py 2024-08-19 16:50:46 +02:00
pancake 57b1b68929 Add -e decai.lang to choose output langauge, defaults to C 2024-08-08 17:05:44 +02:00
pancake 46f1cc0d0d Release 0.8.2 2024-08-08 16:45:46 +02:00
pancake 94837e7fa7 Implement decai -e to configure endpoint and commands 2024-08-07 12:44:24 +02:00
pancake bc1dcee512 Pin llama-cpp-python and use py3.12
* Turns out llama devs publish src before bins
* That breaks r2ai installs, so better pin safe versions
2024-08-07 12:01:02 +02:00
pancake 54a917dc77 Improved decai plugin, more flags, better prompt, etc 2024-08-07 11:30:55 +02:00
pancake 5abfc1e47d Missing r2 2024-08-07 10:41:39 +02:00
pancake 370487c734 Rebrand pdc-ai to decai and add makefile machinery 2024-08-07 10:41:37 +02:00
pancake 56a9c4cc90 Missing import 2024-08-04 22:25:47 +02:00
Richard Patel d8082aa1b8 Fix the 'b'eeping issue on linux terminals 2024-07-18 18:21:27 +02:00
Richard Patel c60c6595be Add make.bat for windows users 2024-07-18 17:45:12 +02:00
Richard Patel 02d86c52ae Padding prompt offset 2024-07-18 13:48:06 +02:00
Richard Patel 12b4849852 Fix error 2024-07-18 13:45:09 +02:00
pancake 18d5e91091 Remove debug prints 2024-07-17 11:17:58 +02:00
pancake f28f95d69c Switch from vectordb to chromadb 2024-07-17 11:16:13 +02:00
pancake a5948e1a40 Improve web scrappers 2024-07-17 11:16:06 +02:00
pancake 79420dd0b8 Add duckduck go scrapping scripts 2024-07-17 02:38:19 +02:00
pancake 43da8f355e Implement -j command to generate json for given prompt 2024-07-17 02:37:46 +02:00
pancake 413ef3e196 Support <<EOF style slurpin syntax for the shell 2024-07-16 18:16:16 +02:00
pancake 030ff0e8f7 Fix the llama3 prompt generation 2024-07-16 16:56:40 +02:00
226 changed files with 20192 additions and 4366 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- buildsystem
+162 -39
View File
@@ -1,47 +1,170 @@
# TODO: add windows
name: ci
name: CI and Release
env:
R2V: 5.9.0
R2V: 6.2.6
on:
push:
pull_request:
workflow_dispatch:
branches:
- '**'
tags:
- '[0-9]*'
# pull_request:
jobs:
build-linux:
runs-on: ubuntu-22.04
build:
name: Build (${{ matrix.os }} / ${{ matrix.build_system }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-latest, windows-latest]
build_system: [make, meson]
exclude:
- os: windows-latest
build_system: make
steps:
- uses: actions/checkout@v4
- name: Installing radare2
run: |
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2_${R2V}_amd64.deb"
wget "https://github.com/radareorg/radare2/releases/download/${R2V}/radare2-dev_${R2V}_amd64.deb"
sudo dpkg -i "radare2_${R2V}_amd64.deb"
sudo dpkg -i "radare2-dev_${R2V}_amd64.deb"
- name: Setting up the Python virtual environment
run: |
python -m venv venv
. venv/bin/activate
pip install pylint
- name: Install Python dependencies
run: |
. venv/bin/activate
pip install -r requirements.txt
- name: Linting
run: |
. venv/bin/activate
make cilint
- name: Running r2ai from the shell
run: |
. venv/bin/activate
python main.py -h
- name: Running r2ai from r2pipe
run: |
. venv/bin/activate
r2 -q -c '#!pipe python main.py -h' /bin/ls
- name: Running r2ai from r2
run: |
. venv/bin/activate
r2 -q -i main.py -c r2ai /bin/ls
- name: Checkout
uses: actions/checkout@v7
- name: Setup Meson/Ninja
if: matrix.build_system == 'meson'
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 Make
if: matrix.build_system == 'make'
run: make -C src
- name: Build with Meson (Unix)
if: matrix.build_system == 'meson' && matrix.os != 'windows-latest'
run: |
cd src
meson setup build || meson configure build
meson compile -C build
- name: Build with Meson (Windows)
if: matrix.build_system == 'meson' && matrix.os == 'windows-latest'
shell: powershell
run: |
cd src
$prefix = "${{ 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
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: 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: bindist-${{ matrix.os }}
path: r2ai-*.zip
if-no-files-found: error
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: [bindist, windows-bindist]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Verify tag is on master
run: |
git fetch origin master:refs/remotes/origin/master
if ! git branch -r --contains ${GITHUB_SHA} | grep -q "origin/master"; then
echo "Tag commit is not on origin/master — skipping release."
exit 0
fi
- name: Download 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/
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
files: |
release-artifacts/*.zip
release-artifacts/decai.r2.js
generate_release_notes: true
+16 -1
View File
@@ -1,2 +1,17 @@
build
venv
b
src/b
src/r2ai
src/r2ai.dSYM
venv
**/__pycache__
*.pyc
*.pyo
*.o
*.so
*.dylib
*.a
third_party
# r2pm binary packages
r2ai-*.zip
+162
View File
@@ -0,0 +1,162 @@
#!/bin/sh
OPENAPI_HOST=localhost
OPENAPI_PORT=8080
if [ -z "${OLLAMA_HOST}" ]; then
OLLAMA_HOST=localhost
fi
if [ -z "${OLLAMA_PORT}" ]; then
OLLAMA_PORT=11434
fi
if [ -z "${OLLAMA_MODEL}" ]; then
OLLAMA_MODEL="llama3.2:1b"
fi
GEMINI_KEY=""
GEMINI_MODEL="gemini-1.5-flash"
if [ -f ~/.r2ai.gemini-key ]; then
GEMINI_KEY=$(cat ~/.r2ai.gemini-key)
fi
OPENAI_KEY=""
OPENAI_MODEL="gpt-4o"
if [ -f ~/.r2ai.openai-key ]; then
OPENAI_KEY=$(cat ~/.r2ai.openai-key)
fi
CLAUDE_KEY=""
CLAUDE_MODEL="claude-sonnet-5-5"
if [ -f ~/.r2ai.anthropic-key ]; then
CLAUDE_KEY=$(cat ~/.r2ai.anthropic-key)
fi
DEEPSEEK_KEY=""
DEEPSEEK_MODEL="claude-sonnet-5-5"
if [ -f ~/.r2ai.deepseek-key ]; then
DEEPSEEK_KEY=$(cat ~/.r2ai.anthropic-key)
fi
SCISSORS="------------8<------------"
read_INPUT() {
export INPUT=`(echo "$ARG" ; echo "<INPUT>"; cat ; echo "</INPUT>" ) | jq -R -s .`
echo "$INPUT"
}
claude() {
read_INPUT
PAYLOAD="
{
\"model\": \"${CLAUDE_MODEL}\",
\"max_tokens\": 5128,
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
}
"
echo "$SCISSORS"
curl -s https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "x-api-key: ${CLAUDE_KEY}" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.content[0].text'
echo "$SCISSORS"
}
ollama() {
read_INPUT
PAYLOAD="{ \"stream\":false, \"model\":\"${OLLAMA_MODEL}\", \"messages\": [{\"role\":\"user\", \"content\": ${INPUT} }]}"
echo "$SCISSORS"
curl -s "http://${OLLAMA_HOST}:${OLLAMA_PORT}/api/chat" \
-H "Content-Type: application/json" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .message.content
echo "$SCISSORS"
}
openapi() {
read_INPUT
PAYLOAD="{ \"prompt\": ${INPUT} }"
echo "$SCISSORS"
curl -s "http://${OPENAPI_HOST}:${OPENAPI_PORT}/completion" \
-H "Content-Type: application/json" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .content
echo "$SCISSORS"
}
openai() {
read_INPUT
PAYLOAD="
{
\"model\": \"${OPENAI_MODEL}\",
\"max_completion_tokens\": 5128,
\"messages\": [ { \"role\": \"user\", \"content\": ${INPUT} } ]
}
"
echo "$SCISSORS"
curl -s https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${OPENAI_KEY}" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r '.choices[0].message.content'
echo "$SCISSORS"
}
gemini() {
read_INPUT
PAYLOAD=" {
\"contents\":[{
\"parts\":[
{\"text\": ${INPUT}}
] }] }"
echo "$SCISSORS"
curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${GEMINI_KEY}" \
-H "Content-Type: application/json" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .candidates[0].content.parts[0].text
echo "$SCISSORS"
}
deepseek() {
read_INPUT
PAYLOAD=" {
\"model\":\"deepseek-chat\",
\"stream\":\"false\",
\"messages\":[{
{\"role\": \"user\", \"content\": ${INPUT}}
}]}"
echo "$SCISSORS"
curl -s -X POST "https://api.deepseek.com/chat/completions" \
-H "Authorization: Bearer ${DEEPSEEK_KEY}" \
-H "Content-Type: application/json" \
-d "`printf '%s\n' \"${PAYLOAD}\"`" | jq -r .choices[0].message.content
echo "$SCISSORS"
}
show_help() {
cat <<EOF
$ ai [--] | [-h] | [prompt] < INPUT
-h = show this help message
-- = don't display the ---8<--- lines in the output
SHAI_API = ollama | gemini | claude | openai
OLLAMA_MODEL=hf.co/mradermacher/salamandra-7b-instruct-aina-hack-GGUF:salamandra-7b-instruct-aina-hack.Q4_K_M.gguf
OLLAMA_HOST=localhost
OLLAMA_PORT=11434
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
EOF
exit 0
}
[ "$1" = "-h" ] && show_help
if [ "$1" = "--" ]; then
SCISSORS=""
shift
fi
export ARG="$@"
case "${SHAI_API}" in
gemini|google) gemini ; ;;
deepseek) deepseek ; ;;
openapi) openapi ; ;;
claude) claude ; ;;
ollama) ollama ; ;;
openai) openai ; ;;
*) claude ; ;;
esac
File renamed without changes.
+14
View File
@@ -0,0 +1,14 @@
SHELL=/bin/bash
MODELS=gpt-oss:20b gemma3:4b gemma3:12b
MODELS+=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
MODELS=granite3-dense:8b mannix/jan-nano:latest yi-coder:latest qwen3:latest deepseek-coder-v2:latest hhao/qwen2.5-coder-tools:latest qwen2.5-coder:latest
LANGS=Dart Perl Ruby Javascript PHP C
all:
for a in ${MODELS} ; do \
declare -a ARGS; \
for l in ${LANGS} ; do \
ARGS+=("echo \"decai -e model=$$a;decai -e lang=$$l;s 0x100000924;af;decai -d\" | r2 -q /bin/ls"); \
done ; \
./run.sh "$${ARGS[@]}" > ${a//\//_}.html ; done
+4
View File
@@ -0,0 +1,4 @@
<b>
decai -e model=$a;decai -e lang=$l;s 0x100000924;af;decai -d
</b><br/>
Transform this pseudocode and respond ONLY with plain code (NO explanations, comments or markdown), Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:', Reduce lines of code and fit everything in a single function, Remove all dead code
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
# Find all directories containing BENCH.txt
dirs=$(find . -name "BENCH.txt" -type f | xargs dirname | sort | uniq)
# Start HTML output
cat <<EOF > benchmark_index.html
<html>
<table>
EOF
# Process each directory
for dir in $dirs; do
cat <<EOF >> benchmark_index.html
<tr> <td>
<div>
EOF
# Add contents of BENCH.txt
cat "$dir/BENCH.txt" >> benchmark_index.html
cat <<EOF >> benchmark_index.html
</div>
</td>
<td>
EOF
# Find and add links to HTML files
for html_file in $(ls "$dir"/*.html 2>/dev/null | sort); do
base=$(basename "$html_file" .html)
cat <<EOF >> benchmark_index.html
<a href="$dir/$base.html">$base</a>
<br />
EOF
done
cat <<EOF >> benchmark_index.html
</td>
</tr>
EOF
done
# End HTML output
cat <<EOF >> benchmark_index.html
</table>
<br />
<br />
</html>
EOF
+177
View File
@@ -0,0 +1,177 @@
#!/bin/bash
# Generate HTML output for command executions with collapsible sections and width control
if [ $# -eq 0 ]; then
echo "Usage: $0 command1 [command2 ...]"
exit 1
fi
# Timeout in seconds for each command (configurable via TIMEOUT environment variable)
TIMEOUT=${TIMEOUT:-30}
cat << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Command Execution Results</title>
<style>
* {
box-sizing: border-box;
}
:root {
--max-width: 400px;
}
body {
font-family: monospace;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
overflow-x: hidden;
}
.slider-container {
background: white;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
width: 100%;
}
.slider-container label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
.slider-container input {
width: 100%;
}
.commands-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: flex-start;
}
.command-section {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
width: var(--max-width);
}
.command-title {
background: #e9ecef;
padding: 12px 15px;
cursor: pointer;
border: none;
width: 100%;
text-align: left;
font-family: inherit;
font-size: inherit;
border-radius: 8px 8px 0 0;
position: relative;
}
.command-title:hover {
background: #dee2e6;
}
.command-time {
position: absolute;
top: 12px;
right: 15px;
color: #6c757d;
font-size: 0.9em;
}
.command-body {
padding: 15px;
border-top: 1px solid #dee2e6;
display: none;
}
.command-output {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-x: auto;
}
.command-section.expanded .command-body {
display: block;
}
</style>
</head>
<body>
<div class="slider-container">
<label for="width-slider">Maximum Width: <span id="width-value">400</span>px</label>
<input type="range" id="width-slider" min="200" max="2000" value="400" step="50">
</div>
<div class="commands-container">
EOF
# Process each command
for cmd in "$@"; do
# Extract title: if command starts with #, use the rest as title, else use the command
if [[ "$cmd" =~ ^#[[:space:]]*(.*) ]]; then
title="${BASH_REMATCH[1]}"
exec_cmd="${cmd#*#}"
exec_cmd="${exec_cmd#"${exec_cmd%%[![:space:]]*}"}" # trim leading spaces
else
title="$cmd"
exec_cmd="$cmd"
fi
# Execute command and time it
start_time=$(date +%s.%N)
tmpfile=$(mktemp)
eval "$exec_cmd" > "$tmpfile" 2>&1 &
pid=$!
(sleep $TIMEOUT && kill $pid 2>/dev/null) &
wait $pid 2>/dev/null
output=$(cat "$tmpfile")
rm "$tmpfile"
exit_code=$?
end_time=$(date +%s.%N)
# Calculate execution time
execution_time=$(echo "$end_time - $start_time" | bc -l 2>/dev/null || echo "0")
if [ $? -ne 0 ]; then
execution_time="N/A"
else
execution_time=$(printf "%.3f" "$execution_time")
fi
# Escape HTML in output
escaped_output=$(echo "$output" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
cat << EOF
<div class="command-section expanded">
<button class="command-title" onclick="toggleSection(this)">$title<span class="command-time">${execution_time}s</span></button>
<div class="command-body">
<div class="command-output">$escaped_output</div>
</div>
</div>
EOF
done
cat << 'EOF'
</div>
<script>
// Slider functionality
const slider = document.getElementById('width-slider');
const widthValue = document.getElementById('width-value');
slider.addEventListener('input', function() {
const value = this.value;
document.documentElement.style.setProperty('--max-width', value + 'px');
widthValue.textContent = value;
});
// Toggle functionality
function toggleSection(button) {
const section = button.parentElement;
section.classList.toggle('expanded');
}
</script>
</body>
</html>
EOF
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+6
View File
@@ -0,0 +1,6 @@
import google.generativeai as genai
genai.configure(api_key="YOUR_API_KEY")
model = genai.GenerativeModel("gemini-1.5-flash")
response = model.generate_content("Explain how AI works")
print(response.text)
File renamed without changes.
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
r2pm -r llama-server \
--port 8012 -ngl 99 -fa -ub 1024 -b 1024 -dt 0.1 \
--ctx-size 0 --cache-reuse 256 -m ~/.r2ai.models/qwen2.5-coder-7b-instruct-q3_k_m.gguf
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
#
# Launch r2ai and use this prompt to generate the conversation from data.txt
# Copypaste the output into podcast.txt
#
# -f /tmp/data.txt Create a script for a podcast between two technical people named Sam and Max, about the changelog on radare2 5.9.4, focus on important features and stuff that impacts positively to users. The length must be as long as possible. Do not show any "**" section, output must contain only the conversation, be emotional and make the two people ask questions to learn more about the details of each feature
if [ ! -f podcast.txt ]; then
echo "Missing podcast.txt. Please read the script before running it."
exit 1
fi
cat podcast.txt | sed -e 's/Sam:/[[pbas 40]]/g' -e 's/Max:/[[pbas 60]]/g' > podcast.say.txt
say -f podcast.say.txt -o podcast.aiff
ffmpeg -i podcast.aiff podcast.mp3
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
# pip install ddgr
ddgr --json $@
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
URLS=`./scrap-ddg.sh -n2 -- $@ |jq '.[].url'`
for URL in ${URLS}; do
eval ./scrap-web.sh ${URL}
done
@@ -7,4 +7,4 @@ fi
# https://github.com/aaronsw/html2text =>
# https://raw.githubusercontent.com/aaronsw/html2text/master/README.md
curl -s "$1" | html2text -width 1024 -utf8 -nobs | grep -E '.{80}' | grep -v '=====' | grep -v '\*\*\*\*'|less
curl -s "$1" | html2text -width 1024 -utf8 -nobs | grep -E '.{80}' | grep -v '=====' | grep -v '\*\*\*\*' | grep -v -e '^\d' | sed -r 's/^[[:space:]]+|[[:space:]]+$//g' | grep -v ://
File renamed without changes.
+68
View File
@@ -0,0 +1,68 @@
import llama_cpp
model_path="/Users/pancake/.r2ai.models/";
# model_path+="unsloth.Q4_K_M.gguf"
# model_path+= "llama-3-tsuki-unsloth-8b.Q5_K_M.gguf"
model_path+= "llama-2-7b-chat-codeCherryPop.Q5_K_M.gguf"
# model_path += "mistral-7b-instruct-v0.2.Q5_K_M.gguf"
peers = [
["@kelsy", "act as a twitter user responding in one short sentence your first though on my messages, be constructive and help me discuss ideas"],
["@john", "act as a twitter user, be concise, respond in one short sentence be funny, help me reason my plans"],
["@anna", "respond in one short sentence with philosophical reasoning on my message"],
["@tony", "behave like a shy twitter user, respond with one or two short sentences, as a software developer, respond in short but wise sentence reasoning the best plans for implementing the topic"]
]
logs = []
ai = llama_cpp.Llama(model_path=model_path, verbose=False, n_ctx=8096)
def context(msg):
global logs
ats = [word for word in msg.split() if word.startswith('@')]
ctx = []
for log in logs:
if log == msg:
continue
if len(ats) > 0 and any(at in log for at in ats):
ctx.append(log)
if not log.startswith("@"):
ctx.append(log)
return ctx
def sortedpeers(msg):
global peers
ats = [word for word in msg.split() if word.startswith('@')]
if len(ats) == 0:
return peers
ps = []
for peer in peers:
if any(at in peer[0] for at in ats):
ps.insert(0, peer)
else:
ps.append(peer)
return ps
def chat(msg):
logs.append(msg)
global ai
for peer in sortedpeers(msg):
ctx = ",".join(context(msg))
m = f"[INST]{peer[1]}[/INST] {msg}"
m = f"[INST]{peer[1]}[/INST] Consider this context: {ctx}. Respond to: {msg}```"
m = f"[INST]{peer[1]}[/INST] <s>{ctx}</s>. Respond in one sentence to: {msg}```"
mm = ai(m, max_tokens=-1)
r = mm["choices"][0]["text"]
r = "".join(r.split("\n"))
reply = f"{peer[0]}: {r}"
logs.append(reply)
print(f"\x1b[31m{reply}\x1b[0m")
# res = ai("Hello")
# print(res["choices"][0]["text"])
while True:
msg = input()
if not msg:
break
chat(msg)
File renamed without changes.
BIN
View File
Binary file not shown.
+66
View File
@@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define ALPHABET_SIZE 26
// Function to perform substitution (both encryption and decryption)
void substitute(const char *input, const char *substitution, char *output, int reverse) {
char mapping[ALPHABET_SIZE];
if (reverse) {
// Create reverse substitution table for decryption
for (int i = 0; i < ALPHABET_SIZE; i++) {
mapping[substitution[i] - 'A'] = 'A' + i;
}
} else {
// Use the direct substitution table for encryption
for (int i = 0; i < ALPHABET_SIZE; i++) {
mapping[i] = substitution[i];
}
}
for (int i = 0; input[i] != '\0'; i++) {
char c = input[i];
// Handle uppercase letters
if (isupper(c)) {
output[i] = mapping[c - 'A'];
}
// Handle lowercase letters
else if (islower(c)) {
output[i] = tolower(mapping[c - 'a']);
}
// Leave non-alphabet characters unchanged
else {
output[i] = c;
}
}
output[strlen(input)] = '\0'; // Null-terminate the output string
}
int main() {
char input[256];
char output[256];
// Configurable substitution key (A-Z)
const char *substitution = "QWERTYUIOPASDFGHJKLZXCVBNM"; // Example substitution key
// Get input from the user
printf("Enter text to encrypt: ");
fgets(input, sizeof(input), stdin);
// Remove newline character from input if it exists
input[strcspn(input, "\n")] = 0;
// Perform encryption (forward substitution)
substitute(input, substitution, output, 0);
printf("Encrypted text: %s\n", output);
// Perform decryption (reverse substitution)
substitute(output, substitution, input, 1);
printf("Decrypted text: %s\n", input);
return 0;
}
File renamed without changes.
+28
View File
@@ -0,0 +1,28 @@
from transformers import AutoTokenizer
import transformers
import torch
model = "facebook/llm-compiler-13b"
model = "QuantFactory/llm-compiler-7b-GGUF"
# model = "file:///tmp/llm-compiler.gguf"
tokenizer = AutoTokenizer.from_pretrained(model)
pipeline = transformers.pipeline(
"text-generation",
model=model,
torch_dtype=torch.float16,
device_map="auto",
)
sequences = pipeline(
'%3 = alloca i32, align 4',
do_sample=True,
top_k=10,
temperature=0.1,
top_p=0.95,
num_return_sequences=1,
eos_token_id=tokenizer.eos_token_id,
max_length=200,
)
for seq in sequences:
print(f"Result: {seq['generated_text']}")
View File
File renamed without changes.
View File
File renamed without changes.
+249
View File
@@ -0,0 +1,249 @@
import Foundation
// Token representation
struct Token {
var token: String
var count: Int
var df: Int
}
// KD-Tree Node
class KDNodeData {
let point: [Float]
let text: String
var left: KDNodeData?
var right: KDNodeData?
let splitDim: Int
init(point: [Float], text: String, left: KDNodeData? = nil, right: KDNodeData? = nil, splitDim: Int) {
self.point = point
self.text = text
self.left = left
self.right = right
self.splitDim = splitDim
}
}
// Result for K-Nearest Neighbors
struct KNNResult {
let node: KDNodeData
let distanceSq: Float
}
class VectorDB {
// Core database structures
private let dimension: Int
private var root: KDNodeData?
private var tokens: [Token] = []
private var totalDocs: Int = 0
private var size: Int = 0
init(dimension: Int) {
self.dimension = dimension
}
// Validate token (similar to C implementation)
private func isValidToken(_ token: String) -> Bool {
let invalidTokens = ["pancake", "author", "radare2"]
return !invalidTokens.contains(token)
}
// Find a token in the token list
private func findToken(_ tokens: [Token], _ token: String) -> Token? {
return tokens.first { $0.token == token }
}
// Compute embedding using TF-IDF approach
private func computeEmbedding(_ text: String) -> [Float] {
// Zero the embedding vector
var embedding = [Float](repeating: 0.0, count: dimension)
// Tokenize and normalize
let words = text
.lowercased()
.replacingOccurrences(of: "[^a-z0-9\\s]", with: " ", options: .regularExpression)
.split(separator: " ")
.map { String($0) }
.filter { !$0.isEmpty }
// Local document token frequency
var docTokens: [Token] = []
for token in words {
if let existingToken = findToken(docTokens, token) {
let index = docTokens.firstIndex { $0.token == token }!
docTokens[index].count += 1
} else {
docTokens.append(Token(token: token, count: 1, df: 0))
}
}
// Increment total documents
totalDocs += 1
// Update global document frequencies
for docToken in docTokens {
if let globalToken = findToken(self.tokens, docToken.token) {
if isValidToken(docToken.token) {
let index = self.tokens.firstIndex { $0.token == docToken.token }!
self.tokens[index].count += 1
self.tokens[index].df += 1
}
} else {
self.tokens.append(Token(token: docToken.token, count: docToken.count, df: 1))
}
}
// Compute TF-IDF and update embedding
for docToken in docTokens {
// Compute term frequency
let tf = 1 + log(Float(docToken.count))
// Find global token to get document frequency
let dfValue = findToken(self.tokens, docToken.token)?.df ?? 1
// Compute inverse document frequency
let idf = log(Float(totalDocs + 1) / Float(dfValue + 1)) + 1
// Compute weight
let weight = tf * idf
// Hash-based index assignment
let hash = simpleHash(docToken.token)
let index = hash % dimension
// Add weighted value to embedding
embedding[index] += weight
}
// L2 Normalize the embedding
return normalizeVector(embedding)
}
// Simple string hash function
private func simpleHash(_ str: String) -> Int {
var hash = 0
for char in str.unicodeScalars {
hash = ((hash << 5) - hash) + Int(char.value)
hash = hash & hash // Convert to 32-bit integer
}
return abs(hash)
}
// Normalize vector to unit length
private func normalizeVector(_ vec: [Float]) -> [Float] {
let normSq = vec.reduce(0) { $0 + $1 * $1 }
if normSq > 0 {
let norm = sqrt(normSq)
return vec.map { $0 / norm }
}
return vec
}
// Compute squared Euclidean distance
private func squaredDistance(_ a: [Float], _ b: [Float]) -> Float {
guard a.count == b.count else { return Float.greatestFiniteMagnitude }
return zip(a, b).reduce(0) { $0 + pow($1.0 - $1.1, 2) }
}
// Recursive KD-Tree insertion
private func insertRecursive(_ node: KDNodeData?, point: [Float], text: String, depth: Int) -> KDNodeData {
guard let currentNode = node else {
return KDNodeData(point: point, text: text, splitDim: depth % dimension)
}
let axis = currentNode.splitDim
if point[axis] < currentNode.point[axis] {
currentNode.left = insertRecursive(currentNode.left, point: point, text: text, depth: depth + 1)
} else {
currentNode.right = insertRecursive(currentNode.right, point: point, text: text, depth: depth + 1)
}
return currentNode
}
// Insert a document into the vector database
func insert(_ text: String) {
guard !text.isEmpty else { return }
// Compute embedding
let embedding = computeEmbedding(text)
// Insert into KD-Tree
root = insertRecursive(root, point: embedding, text: text, depth: 0)
size += 1
}
// K-Nearest Neighbors search
func query(_ text: String, k: Int) -> [String] {
guard !text.isEmpty, root != nil else { return [] }
// Compute query embedding
let queryEmbedding = computeEmbedding(text)
// Initialize result set
var results: [KNNResult] = []
// Recursive KNN search
func searchRecursive(_ node: KDNodeData?, query: [Float], depth: Int) {
guard let node = node else { return }
// Compute distance and potentially insert into results
let distSq = squaredDistance(query, node.point)
if results.count < k {
results.append(KNNResult(node: node, distanceSq: distSq))
// Maintain max-heap property
results.sort { $0.distanceSq > $1.distanceSq }
} else if distSq < results[0].distanceSq {
results[0] = KNNResult(node: node, distanceSq: distSq)
results.sort { $0.distanceSq > $1.distanceSq }
}
// Decide which subtree to search first
let axis = node.splitDim
let diff = query[axis] - node.point[axis]
let first = diff < 0 ? node.left : node.right
let second = diff < 0 ? node.right : node.left
// Recursively search
searchRecursive(first, query: query, depth: depth + 1)
// Potentially search the other subtree
if results.count < k || diff * diff < results[0].distanceSq {
searchRecursive(second, query: query, depth: depth + 1)
}
}
// Perform the search
searchRecursive(root, query: queryEmbedding, depth: 0)
// Sort results and return texts
return results
.sorted { $0.distanceSq < $1.distanceSq }
.map { $0.node.text }
}
// Get total number of documents
func getSize() -> Int {
return size
}
}
// Example usage
func main() {
// Create a vector database with 64-dimensional embeddings
let db = VectorDB(dimension: 64)
// Insert some sample documents
db.insert("The quick brown fox jumps over the lazy dog")
db.insert("A quick brown fox is very quick")
db.insert("The author of radare2 is name / named pancake")
db.insert("The lazy dog sleeps all day")
// Query for similar documents
let results = db.query("who is the author of radare2?", k: 2)
print("Similar documents:", results)
}
// Run example
main()
+81
View File
@@ -0,0 +1,81 @@
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
PWD=$(shell pwd)
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
PV=3.12
ifeq ($(shell which python${PV} > /dev/null && echo ok),ok)
PYTHON?=python${PV}
else
PYTHON?=python3
endif
PIP=$(PYTHON) -m pip
ifeq ($(R2PM_BINDIR),)
MISSING RADARE2
endif
LINTED=r2ai/code_block.py
LINTED+=r2ai/bubble.py
LINTED+=r2ai/const.py
LINTED+=r2ai/backend/kobaldcpp.py
# LINTED+=r2ai/index.py
# LINTED+=r2ai/voice.py
# LINTED+=r2ai/anthropic.py
r2ai-python py python r2aipy: venv
@./r2ai.sh
large:
. venv/bin/activate ; $(PYTHON) -m r2ai.cli -l
all.old:
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
$(PYTHON) main.py || $(MAKE) deps
venv:
$(PYTHON) -m venv venv
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install . ; fi
deps: venv
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
pip install --force-reinstall -U --no-cache-dir .
clean:
rm -rf venv
rm -rf build
find . -name "*.egg-info" -exec rm -rf {} +
user-install:
rm -f $(R2PM_BINDIR)/r2ai
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
install: user-install
-mkdir -p /usr/local/share/man/man1/r2ai.1
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
install-plugin user-install-plugin:
ln -fs $(PWD)/r2ai/plugin.py $(R2_USER_PLUGINS)/r2ai.py
uninstall user-uninstall:
rm -f $(R2PM_BINDIR)/r2ai
-rm -f /usr/local/share/man/man1/r2ai.1
user-uninstall-plugin uninstall-plugin:
rm -f $(R2_USER_PLUGINS)/r2ai.py
pub:
$(PYTHON) -m build
twine check dist/*
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
pylint lint cilint:
. venv/bin/activate ; pylint $(LINTED)
lintall:
pylint *.py r2ai/*.py
.PHONY: lint lintall
deps-global:
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
$(PIP) install --force-reinstall -U --break-system-packages --no-cache-dir .
+155
View File
@@ -0,0 +1,155 @@
# r2ai-python
This directory contains the original implementation of the AI support for radare2 written in Python.
It is considered _deprecated_, but it's still useful and aims to be maintained, we recommend using decai or the C rewrite as alternatives.
The Python implementation consists in a module can be used in 3 different ways:
* commandline repl using r2pipe to spawn or connect to remote radare2 instances
* radare2 core plugin that is instantiated when placing it in the radare2 plugin's directory
* Python API to be used for writing your own scripts
## API Providers
The Python implementation focused on supporting the LlamaCpp Python module, which is somehow heavy to load in many environments, but it also supports litellm which provides access to many external connectors like ollama, openai, anthropic, gemini, etc.
## Deprecation Reasons
There are several reasons why this implementation is considered deprecated and not recommended to use:
1) It's Python.
- Aka, it's slow, heavy loads in same process
- Your system probably have multiple versions installed
- The shell and radare2 python bindings are probably not the same
- Required venv to be created (at least an extra 1GB)
- Not typed, regressions and bugs appear at runtime
2) Goto 1
### Windows
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
```cmd
git clone https://github.com/radareorg/r2ai
cd r2ai
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
python3 -m pip install .
python3 main.py
```
### Selecting the model
- List all downloaded models: `-m`
- Get a short list of models: `-MM`
- Help: `-h`
**Example selecting a remote models:**
```
[r2ai:0x00006aa0]> -m anthropic:claude-sonnet-5-5
[r2ai:0x00006aa0]> -m openai:gpt-4
```
**Example downloading a free local AI: Mistral 7B v0.2:**
Launch r2ai, select the model and ask a question. If the model isn't downloaded yet, r2ai will ask you which precise version to download.
```
[r2ai:0x00006aa0]> -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
```
Then ask your question, and r2ai will automatically download if needed:
```
[r2ai:0x00006aa0]> give me a short algorithm to test prime numbers
Select TheBloke/Mistral-7B-Instruct-v0.2-GGUF model. See -M and -m flags
[?] Quality (smaller is faster):
> Small | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
Medium | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
Large | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
See More
[?] Quality (smaller is faster):
> mistral-7b-instruct-v0.2.Q2_K.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
mistral-7b-instruct-v0.2.Q3_K_L.gguf | Size: 3.6 GB, Estimated RAM usage: 6.1 GB
mistral-7b-instruct-v0.2.Q3_K_M.gguf | Size: 3.3 GB, Estimated RAM usage: 5.8 GB
mistral-7b-instruct-v0.2.Q3_K_S.gguf | Size: 2.9 GB, Estimated RAM usage: 5.4 GB
mistral-7b-instruct-v0.2.Q4_0.gguf | Size: 3.8 GB, Estimated RAM usage: 6.3 GB
mistral-7b-instruct-v0.2.Q4_K_M.gguf | Size: 4.1 GB, Estimated RAM usage: 6.6 GB
mistral-7b-instruct-v0.2.Q4_K_S.gguf | Size: 3.9 GB, Estimated RAM usage: 6.4 GB
mistral-7b-instruct-v0.2.Q5_0.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
mistral-7b-instruct-v0.2.Q5_K_M.gguf | Size: 4.8 GB, Estimated RAM usage: 7.3 GB
mistral-7b-instruct-v0.2.Q5_K_S.gguf | Size: 4.7 GB, Estimated RAM usage: 7.2 GB
mistral-7b-instruct-v0.2.Q6_K.gguf | Size: 5.5 GB, Estimated RAM usage: 8.0 GB
mistral-7b-instruct-v0.2.Q8_0.gguf | Size: 7.2 GB, Estimated RAM usage: 9.7 GB
[?] Use this model by default? ~/.r2ai.model:
> Yes
No
[?] Download to ~/.local/share/r2ai/models? (Y/n): Y
```
**Example selecting a local model served by Ollama**
Download a model and make it available through Ollama:
```
$ ollama ls
NAME ID SIZE MODIFIED
codegeex4:latest 867b8e81d038 5.5 GB 23 hours ago
```
Use it from r2ai by prefixing its name with `ollama/`
```
[r2ai:0x00002d30]> -m ollama/codegeex4:latest
[r2ai:0x00002d30]> hi
Hello! How can I assist you today?
```
### Standard/Auto mode
The standard mode is invoked by directly asking the question.
For the Auto mode, the question **must be prefixed** by `' ` (quote + space). The AI may instruct r2ai to run various commands. Those commands are run on *your host*, so you will be asked to review them before they run.
Example in "standard" mode:
```
[r2ai:0x00006aa0]> compute 4+5
4 + 5 = 9
[r2ai:0x00006aa0]> draw me a pancake in ASCII art
Sure, here's a simple ASCII pancake:
_____
( )
( )
-----
```
Example in auto mode:
```
[r2ai:0x00006aa0]>' Decompile the main
[..]
r2ai is going to execute the following command on the host
Want to edit? (ENTER to validate) pdf @ fcn.000015d0
This command will execute on this host: pdf @ fcn.000015d0. Agree? (y/N) y
```
If you wish to edit the command, you can do it inline for short one line commands, or an editor will pop up.
### r2ai Configuration settings
List all settings with `-e`
| Key | Explanation |
| ----------- | ------------------------------------- |
| debug_level | All verbose messages for level 1. Default is 2 |
| auto.max_runs | Maximum number of questions the AI is allowed to ask r2 in auto mode. |
| auto.hide_tool_output | By default false, consequently output of r2cmd, run_python etc is shown. Set to `true` to hide those internal messages. |
| chat.show_cost | Show the cost of each request to the AI if true |
+35 -23
View File
@@ -1,7 +1,7 @@
[project]
name = "r2ai"
dynamic = ["readme"]
version = "0.8.0"
version = "0.9.6"
description = "Artificial intelligence tooling for radare2"
license = {text = "MIT License"}
@@ -9,44 +9,56 @@ authors = [
{name = "pancake", email = "[email protected]"}
]
[project.urls]
homepage = "https://www.radare.org/"
repository = "https://github.com/radareorg/r2ai"
[project.scripts]
r2ai = "r2ai:main"
[tool.setuptools.packages.find]
where = [".", "r2ai"]
include = ["main", "r2ai"]
namespaces = true
[tool.setuptools.dynamic]
readme = {file = "README.md", content-type = "text/markdown"}
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
dependencies = [
"rich",
"r2pipe",
"inquirer",
"llama-cpp-python",
"huggingface_hub==0.22.0",
"llama-cpp-python==0.3.7",
"huggingface_hub",
"appdirs",
"unidecode",
"jsonref",
"transformers",
"pydantic",
"torch"
"pyreadline3",
"tokentrim",
"boto3",
"colorama",
"textual",
"litellm>=1.60.5",
"numpydoc"
]
[project.optional-dependencies]
extras = [
"chromadb",
"openai",
"anthropic",
"groq",
"google-generativeai",
"google-cloud-aiplatform"
]
[project.urls]
homepage = "https://www.radare.org/"
repository = "https://github.com/radareorg/r2ai"
[project.scripts]
r2ai = "r2ai.main:run"
[tool.setuptools]
include-package-data = true
[tool.setuptools.packages.find]
where = ["."]
include = ["r2ai*"]
namespaces = true
[tool.setuptools.dynamic]
readme = {file = "README.md", content-type = "text/markdown"}
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
File renamed without changes.
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
unset DYLD_LIBRARY_PATH
unset LD_LIBRARY_PATH
export TRANSFORMERS_NO_ADVISORY_WARNINGS=1
PYTHON=python3
if [ -h "$0" ]; then
F=`readlink $0`
else
F="$0"
fi
D=`dirname "$F"`
RD=`realpath "$D"`
[ -n "${RD}" ] && D="$RD"
[ -n "$D" ] && cd "$D"
if [ -d venv ]; then
. venv/bin/activate
else
$PYTHON -m venv venv
. venv/bin/activate
pip3 install -e .
fi
exec $PYTHON -m r2ai.cli "$@"
+24
View File
@@ -0,0 +1,24 @@
import os
import logging
from rich.logging import RichHandler
VERSION = "0.8.2"
# 0 NOTSET, 1 DEBUG, 2 INFO, 3 WARNING, 4 ERROR, 5 CRITICAL; multipied by 10
LOG_LEVEL = int(os.environ.get('R2AI_LOG', '2')) * 10
LOG_FILE = os.environ.get('R2AI_LOGFILE', None)
for tag in ["httpx", "openai", "httpcore"]:
_logger = logging.getLogger(tag)
_logger.setLevel(logging.CRITICAL)
_logger.propagate = False # Disable child loggers too
handlers = [RichHandler()]
if LOG_FILE:
handlers.append(logging.FileHandler(LOG_FILE))
LOGGER = logging.getLogger(__name__)
logging.basicConfig(format="%(name)s - %(levelname)s - %(message)s",
handlers=handlers)
LOGGER.setLevel(LOG_LEVEL)
+511
View File
@@ -0,0 +1,511 @@
import builtins
import json
import sys
import re
from . import LOGGER
import litellm
from litellm import _should_retry, acompletion, utils, ModelResponse
import asyncio
from .pipe import get_filename
from .tools import r2cmd, run_python, execute_binary, schemas, print_tool_call
import json
import signal
from .spinner import spinner
from .completion import create_chat_completion
import uuid
import time
import readline
import subprocess
import shlex
import os
import tempfile
# litellm.drop_params = True
# litellm.set_verbose=True
ANSI_REGEX = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
SYSTEM_PROMPT_AUTO = """
You are a reverse engineer and you are using radare2 to analyze a binary.
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
# Guidelines
- Understand the Task: Grasp the main objective, goals, requirements, constraints, and expected output.
- Reasoning Before Conclusions**: Encourage reasoning steps before any conclusions are reached.
- Assume the user is always asking you about the binary, unless they're specifically asking you for radare2 help.
- The binary has already been loaded. You can interact with the binary using the r2cmd tool.
- `this` or `here` might refer to the current address in the binary or the binary itself.
- If you need more information, try to use the r2cmd tool to run commands before answering.
- You can use the r2cmd tool multiple times if you need or you can pass a command with pipes if you need to chain commands.
- If you're asked to decompile a function, make sure to return the code in the language you think it was originally written and rewrite it to be as easy as possible to be understood. Make sure you use descriptive variable and function names and add comments.
- Don't just regurgitate the same code, figure out what it's doing and rewrite it to be more understandable.
- If you need to run a command in r2 before answering, you can use the r2cmd tool
- Do not repeat commands if you already know the answer.
- Formulate a plan. Think step by step. Analyze the binary as much as possible before answering.
- You must keep going until you have a final answer.
- Double check that final answer. Make sure you didn't miss anything.
- Make sure you call tools and functions correctly.
"""
SYSTEM_PROMPT_AUTO_REASONING = f"""
You are an expert reverse engineer.
The user will ask questions about the binary and you will respond with the answer to the best of your ability.
You can use the available tools to help you answer the user's question.
The binary has already been loaded. You can interact with the binary using the r2cmd tool.
If you need to run a command in r2 before answering, you can use the r2cmd tool
"""
class ChatAuto:
def __init__(self, model, max_tokens = 32000, top_p=0.95, temperature=0.0, interpreter=None, system=None, tools=None, ask_to_execute=True, messages=None, tool_choice='auto', llama_instance=None, timeout=60, stream=True, cb=None, max_runs=100):
self.logger = LOGGER
self.functions = {}
self.tools = []
self.model = model
self.max_tokens = max_tokens
self.top_p = top_p
self.temperature = temperature
self.system = system
self.messages = messages
self.interpreter = interpreter
self.system_message = None
self.timeout = timeout
self.stream = stream
self.cost = 0
self.is_reasoning = False
self.n_runs = 0
self.max_runs = max_runs
self._last_response = None
self._start_time = time.time()
self._last_run_time = time.time()
self.ask_to_execute = ask_to_execute
self.llama_instance = llama_instance or interpreter.llama_instance if interpreter else None
init_prompt = ""
if not self.llama_instance:
rmodel = re.sub(r'ft:([^:]+).*', r'\1', self.model)
model_info = litellm.get_model_info(rmodel)
self.max_tokens = min(self.max_tokens, model_info['max_tokens'])
if self.model.startswith('openai/o'):
self.is_reasoning = True
self.max_tokens = model_info['max_tokens']
self.timeout = 60 * 60
init_commands = self.interpreter.env["auto.init_commands"]
if init_commands:
init_prompt = f"""
Here is some information about the binary to get you started:
> {init_commands}
{r2cmd(init_commands)}
"""
if self.is_reasoning:
if not system and not self.messages[0]['role'] == 'developer':
self.messages.insert(0, { "role": "developer", "content": SYSTEM_PROMPT_AUTO_REASONING + init_prompt })
self.stream = False
else:
if messages and messages[0]['role'] != 'system':
self.messages.insert(0, { "role": "system", "content": system or SYSTEM_PROMPT_AUTO + init_prompt })
if cb:
self.cb = cb
else:
self.cb = lambda *args: None
self.tool_choice = None
if tools:
for tool in tools:
if tool.__name__ in schemas:
schema = schemas[tool.__name__]
else:
schema = utils.function_to_dict(tool)
self.tools.append({ "type": "function", "function": schema })
self.functions[tool.__name__] = tool
self.tool_choice = tool_choice
#self.tool_end_message = '\nNOTE: The user saw this output, do not repeat it.'
async def process_tool_calls(self, tool_calls):
if tool_calls:
for tool_call in tool_calls:
self.logger.debug(f"tool_call: {tool_call}")
tool_name = tool_call["function"]["name"]
if "id" not in tool_call:
tool_call["id"] = str(uuid.uuid4())
try:
tool_args = json.loads(tool_call["function"]["arguments"])
except Exception:
if "arguments" in tool_call["function"] and type(tool_call["function"]["arguments"]) == dict:
tool_args = tool_call["function"]["arguments"]
else:
self.logger.error(f'Error parsing JSON: {tool_call["function"]["arguments"]}')
# raise Exception('Error parsing JSON')
self.messages.append({"role": "tool", "name": tool_name, "content": "Error: Unable to parse JSON" , "tool_call_id": tool_call["id"]})
continue
if tool_name not in self.functions:
self.logger.error(f'Tool not found: {tool_name}')
self.messages.append({"role": "tool", "name": tool_name, "content": "Error: Tool not found" , "tool_call_id": tool_call["id"]})
continue
if self.ask_to_execute:
# tool_call typically contains:
# { 'id': 'toolu_01Hr6XTatRtp7rX54iBu1voC', 'type': 'function', 'function': {'name': 'r2cmd', 'arguments': '{"command": "pdf @ main"}'}}
answer = ''
will_execute = True # a command (modified or not) will be executed
while answer.lower() != 'y':
try:
command = tool_args.get('command', '')
if len(command.splitlines()) > 1:
print(f'r2ai is going to execute the following command on the host:')
print(f'{command}')
want_edit = input('Want to edit? (Y/n) ')
if want_edit.lower() != 'n':
# open editor when several lines
with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
temp_file.write(command)
temp_filename = temp_file.name
# Fallback to 'nano' if EDITOR is not set (I still prefer emacs)
editor = os.environ.get('EDITOR', 'nano')
subprocess.run(shlex.split(editor + ' ' + temp_filename))
with open(temp_filename, "r") as f:
new_command = f.read().strip()
os.remove(temp_filename)
else:
self.logger.debug(f'User agrees with not editing the command: {command}')
answer = 'y'
else:
# inline short edit
print(f'r2ai is going to execute the following command on the host:')
print(f'> {command}')
new_command = input(f"Type a new command or press ENTER to use {command} ") or command
if answer.lower() != 'y':
answer = input(f"\033[91mThis command will execute on this host: {new_command}. Agree? (y/N)\033[0m ")
if answer.lower() == 'y':
# we need to refresh tool_call and tool_args with edited values
tool_args['command'] = new_command
tool_call['function']['arguments'] = json.dumps(tool_args)
except (json.JSONDecodeError, TypeError, AttributeError) as e:
# in case tool_call is not formatted as expected, like missing command, arguments, function...
self.logger.error(f'Unexpected format for {tool_call}')
self.messages.append({"role": "tool", "name": tool_name, "content": "User refused to execute the command" , "tool_call_id": tool_call["id"]})
# error case: we'll skip the tool
will_execute = False
break
if not will_execute:
self.logger.debug(f'Skipping tool={tool_call}')
continue
self.logger.debug(f'Executing tool id={tool_call["id"]}, name={tool_name}, arguments={tool_args}')
self.cb('tool_call', { "id": tool_call["id"], "function": { "name": tool_name, "arguments": tool_args } })
if asyncio.iscoroutinefunction(self.functions[tool_name]):
tool_response = await self.functions[tool_name](**tool_args)
else:
tool_response = self.functions[tool_name](**tool_args)
if self.interpreter.env["auto.hide_tool_output"] == "false":
self.cb('tool_response', { "id": tool_call["id"] + '_response', "content": tool_response })
self.messages.append({"role": "tool", "name": tool_name, "content": ANSI_REGEX.sub('', tool_response), "tool_call_id": tool_call["id"]})
return await self.get_completion()
async def process_streaming_response(self, resp):
tool_calls = []
msgs = []
parts = []
current_message = { "role": "assistant", "content": "", "tool_calls": [] }
first_chunk = True
async for chunk in resp:
delta = None
choice = chunk.choices[0]
delta = choice.delta
if delta.tool_calls:
delta_tool_calls = delta.tool_calls[0]
index = delta_tool_calls.index
fn_delta = delta_tool_calls.function
tool_call_id = delta_tool_calls.id
if len(current_message['tool_calls']) < index + 1:
tool_call = {
"id": tool_call_id or str(uuid.uuid4()),
"type": "function",
"function": {
"name":fn_delta.name,
"arguments": fn_delta.arguments
}
}
current_message['tool_calls'].append(tool_call)
else:
if fn_delta.name:
current_message['tool_calls'][index]["function"]["name"] = fn_delta.name
current_message['tool_calls'][index]["function"]["arguments"] += fn_delta.arguments
else:
m = None
done = False
if delta.content is not None:
if first_chunk:
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
first_chunk = False
m = delta.content
if m is not None:
current_message['content'] += m
self.cb('message', { "content": m, "id": 'message_' + chunk.id, 'done': False })
if 'finish_reason' in choice and choice['finish_reason'] == 'stop':
done = True
self.cb('message', { "content": "", "id": 'message_' + chunk.id, 'done': True })
self.cb('message_stream', { "content": m if m else '', "id": 'message_' + chunk.id, 'done': done })
self.messages.append(current_message)
if len(current_message['tool_calls']) == 0:
del current_message['tool_calls']
self.show_cost()
else:
self.show_cost()
await self.process_tool_calls(current_message['tool_calls'])
return current_message
async def process_response(self, resp):
content = resp.choices[0].message.content
current_message = { 'role': 'assistant', 'content': content or '', 'tool_calls': [] }
for i, tool_call in enumerate(resp.choices[0].message.tool_calls or []):
current_message['tool_calls'].append({
"id": tool_call.id,
"type": "function",
"index": i,
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
},
})
if len(current_message['tool_calls']) == 0:
try:
tool_call = json.loads(content)
if 'name' in tool_call and tool_call['name'] in self.functions:
args = None
if 'arguments' in tool_call:
args = tool_call['arguments']
elif 'parameters' in tool_call:
args = tool_call['parameters']
if args:
current_message['tool_calls'].append({ "id": resp.id, "function": { "name": tool_call['name'], "arguments": json.dumps(args) } })
except Exception:
pass
self.messages.append(current_message)
if current_message['content'] != "":
self.cb('message', { 'content': "\n\x1b[1;32massistant\x1b[0m\n", 'done': True })
self.cb('message', { "content": current_message['content'], "id": 'message_' + resp.id, 'done': True })
if len(current_message['tool_calls']) == 0:
del current_message['tool_calls']
self.show_cost()
else:
self.show_cost()
await self.process_tool_calls(current_message['tool_calls'])
return current_message
async def async_response_generator(self, response):
for item in response:
resp = ModelResponse(stream=True, **item)
yield resp
async def attempt_completion(self):
stream = self.stream
if self.llama_instance:
args = {
"temperature": self.temperature,
"top_p": self.top_p,
"max_tokens": self.max_tokens,
"stream": stream,
}
if self.interpreter.env["chat.rawdog"] == "true":
res = create_chat_completion(self.interpreter, messages=self.messages, tools=self.tools, **args)
else:
res = self.llama_instance.create_chat_completion(self.messages, tools=self.tools, **args)
if args['stream']:
return self.async_response_generator(res)
else:
return ModelResponse(**next(res))
additional_args = {}
if self.is_reasoning:
additional_args['reasoning_effort'] = self.interpreter.env["chat.reasoning_effort"]
else:
additional_args['temperature'] = self.temperature
if self.interpreter.env["chat.openai_store"] == "true":
additional_args['store'] = True
if not self.check_max():
additional_args['tools'] = self.tools
additional_args['tool_choice'] = self.tool_choice
compl = await acompletion(
model=self.model,
messages=self.messages,
timeout=self.timeout,
max_completion_tokens=self.max_tokens,
**additional_args,
stream=stream,
)
return compl
def check_max(self):
if self.max_runs > 0 and self.n_runs >= self.max_runs - 1:
return True
return False
def show_cost(self):
def format_time(seconds):
minutes = int(seconds // 60)
remaining_seconds = int(seconds % 60)
if minutes > 0:
return f"{minutes}m {remaining_seconds}s"
return f"{remaining_seconds}s"
self.n_runs += 1
run_time = format_time(time.time() - self._last_run_time)
self._last_run_time = time.time()
total_time = format_time(time.time() - self._start_time)
if self.interpreter.env["chat.show_cost"] == "true" and self.n_runs > 0:
run_cost = 0
if not self.llama_instance:
run_cost = litellm.completion_cost(completion_response=self._last_response, messages=self.messages, model=self.model)
self.cost += run_cost
cb('usage', {
"model": self.model,
"run_cost": run_cost,
"total_cost": self.cost,
"n_runs": self.n_runs,
"max_runs": self.max_runs,
"run_time": run_time,
"total_time": total_time
})
async def get_completion(self):
stream = self.stream
if self.llama_instance:
response = await self.attempt_completion()
if stream:
return await self.process_streaming_response(response)
else:
return await self.process_response(response)
max_retries = 5
base_delay = 2
for retry_count in range(max_retries):
try:
response = await self.attempt_completion()
self._last_response = response
self.logger.debug(f'chat completion {response}')
res = None
if stream:
res = await self.process_streaming_response(response)
else:
res = await self.process_response(response)
return res
except Exception as e:
self.logger.error(f'Error getting completion: {e}')
if not _should_retry(getattr(e, 'status_code', None)) or retry_count == max_retries - 1:
raise
delay = base_delay * (2 ** retry_count)
self.logger.info(f"Retrying in {delay} seconds...")
await asyncio.sleep(delay)
raise Exception("Max retries reached. Unable to get completion.")
async def achat(self, messages=None) -> str:
if messages:
self.messages = messages
self.logger.debug(self.messages)
response = await self.get_completion()
return response
def chat(self, **kwargs) -> str:
return asyncio.run(self.achat(**kwargs))
def cb(type, data):
spinner.stop()
if type == 'message_stream':
if 'content' in data:
sys.stdout.write(data['content'])
elif type == 'tool_call':
builtins.print()
print_tool_call(data)
elif type == 'tool_response':
if 'content' in data:
sys.stdout.write(data['content'])
sys.stdout.flush()
builtins.print()
elif type == 'usage':
sys.stdout.flush()
builtins.print()
sys.stdout.write(f'\x1b[1;34m{data["model"]} | total: ${float(data["total_cost"]):.10f} | run: ${float(data["run_cost"]):.10f} | {data["n_runs"]} / {data["max_runs"]} | {data["run_time"]} / {data["total_time"]}\x1b[0m')
sys.stdout.flush()
builtins.print()
elif type == 'message' and data['done']:
if data['content']:
sys.stdout.flush()
sys.stdout.write(data['content'])
sys.stdout.flush()
else:
builtins.print()
def signal_handler(signum, frame):
raise KeyboardInterrupt
def chat(interpreter, **kwargs):
model = interpreter.model.replace(":", "/")
tools = [r2cmd, run_python, execute_binary]
messages = interpreter.messages
tool_choice = 'auto'
try:
loop = asyncio.get_event_loop()
if loop.is_closed() or loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
chat_auto = ChatAuto(
model,
max_tokens=int(interpreter.env["llm.maxtokens"]),
top_p=float(interpreter.env["llm.top_p"]),
temperature=float(interpreter.env["llm.temperature"]),
interpreter=interpreter,
tools=tools,
ask_to_execute=interpreter.env["auto.ask_to_execute"] == "true",
messages=messages,
tool_choice=tool_choice,
llama_instance=interpreter.llama_instance,
stream=interpreter.env["chat.stream"] == "true",
max_runs=int(interpreter.env["auto.max_runs"]),
cb=cb
)
original_handler = signal.getsignal(signal.SIGINT)
try:
signal.signal(signal.SIGINT, signal_handler)
spinner.start()
return loop.run_until_complete(chat_auto.achat())
except KeyboardInterrupt:
builtins.print("\033[91m\nOperation cancelled by user.\033[0m")
tasks = asyncio.all_tasks(loop=loop)
for task in tasks:
task.cancel()
try:
loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
loop.run_until_complete(asyncio.sleep(0.1))
except asyncio.CancelledError:
pass
return None
finally:
signal.signal(signal.SIGINT, original_handler)
spinner.stop()
litellm.in_memory_llm_clients_cache.flush_cache()
File renamed without changes.
+106
View File
@@ -0,0 +1,106 @@
from colorama import Fore, Back, Style
from ..pipe import get_r2_inst
BEDROCK_TOOLS_CONFIG = {
"tools": [
{
"toolSpec": {
"name": "r2cmd",
"description": "runs commands in radare2. You can run it multiple times or chain commands with pipes/semicolons. You can also use r2 interpreters to run scripts using the `#`, '#!', etc. commands. The output could be long, so try to use filters if possible or limit. This is your preferred tool",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "command to run in radare2."
}
},
"required": [
"command"
]
}
}
}
}
]
}
def build_messages_for_bedrock(messages):
# Bedrock needs that conversation messages alternate between user and assistant
# if the user wants to send multiple messages they should all be consolidated
# in a single entry
bedrock_msgs = []
for msg in messages:
role = msg.get("role")
if msg.get("role") not in ["user", "assistant"]:
continue
if len(bedrock_msgs) > 0 and bedrock_msgs[-1]["role"] == role:
last_msg = bedrock_msgs[-1]
# This message should be consolidated with the previous one
if isinstance(msg["content"], list):
last_msg["content"].extend(msg["content"])
else:
last_msg["content"].append({
"text": msg["content"]
})
else:
# The role changed, so create a new entry
if isinstance(msg["content"], list) and "role" in msg:
# This clause is for messages that are returned from bedrock
# and thus are already well formatted
bedrock_msgs.append(msg)
else:
bedrock_msgs.append({
"role": role,
"content": [{"text": msg["content"]}]
})
return bedrock_msgs
def extract_bedrock_tool_calls(response):
tool_calls = []
content = response.get("output", {}).get("message", {}).get("content", [])
for msg in content:
if not "toolUse" in msg:
continue
tool_calls.append(msg["toolUse"])
return tool_calls
def process_bedrock_tool_calls(calls):
r2 = get_r2_inst()
messages = []
if not r2:
print("Invalid r2 instance. Can not execute commands. Did you open a file?")
return messages
for call in calls:
if call["name"] == "r2cmd":
cmd = call["input"]["command"]
print(f"\n{Fore.GREEN}Executing r2 cmd: {cmd}{Style.RESET_ALL}")
res = r2.cmd(cmd)
# print(f"{res}")
messages.append({
"role": "user",
"content": [{
"toolResult": {
"toolUseId": call.get("toolUseId"),
"content": [{ "text": res }]
}
}]
})
return messages
def print_bedrock_response(response, print=print, output_limit=200):
msg = response.get("output", {}).get("message", {})
for m in msg.get("content", []):
if "text" in m:
print(f"\n{Fore.YELLOW}[AI]> {Style.RESET_ALL}{m['text']}")
File renamed without changes.
@@ -1,28 +1,38 @@
"""Implementation for kobaldcpp http api call using openai endpoint."""
import json
import requests
from .. import LOGGER
# MODEL="~/.r2ai.models/Lexi-Llama-3-8B-Uncensored_Q4_K_M.gguf"
# ./llama-server --in-prefix '### User: ' --prompt 5001 \
# --in-suffix "### Assistant: " -m $MODEL
def chat(messages, uri='http://localhost:5001'):
def chat(messages, uri='http://localhost:5001', model='gpt-3.5-turbo', openapiKey=''):
"""Send a message to a kobaldcpp server and return the autocompletion response
"""
if uri.endswith("/"):
uri = uri[0:len(uri)-1]
# url = f'{uri}/v1/completions'
url = f'{uri}/v1/chat/completions'
url = f'{uri}/chat/completions'
data = {
"model": "gpt-3.5-turbo",
"model": model,
"messages": messages
}
r = requests.post(url=url, data=json.dumps(data), timeout=600)
# print(r.text)
headers = {
"HTTP-Referer": "https://rada.re", # openrouter specific: Optional, for including your app on openrouter.ai rankings.
"X-Title": "radare2", # openrouter specific: Optional. Shows in rankings on openrouter.ai.
"Authorization": f"Bearer {openapiKey}",
"Content-Type": "application/json"
}
r = requests.post(url=url, data=json.dumps(data), timeout=600, headers=headers)
j = json.loads(r.text)
if "choices" in j:
choice = j["choices"][0]
if "text" in choice:
return j["choices"][0]["text"]
return choice["message"]["content"]
if "error" in j:
error = j["error"]
LOGGER.getChild("openapi").error("OpenAIError[%s], %s", error['code'], error['message'])
return "No response"
@@ -14,6 +14,15 @@ CLIPPY_BEGIN = """
CLIPPY_END = """`-----------------------------------------------------------------------"""
PICO_BEGIN = r"""
__ __
(o |_| o)_______
| _____________)
\ / .
\ / |\
\__________/ | \
.----------------------------------------------------------' '---------"""
USER_BEGIN = """ |\\
| \\
.--' '--"""
@@ -48,6 +57,11 @@ def response_begin():
"""
print(CLIPPY_BEGIN + getpad() + ".")
def pico_begin():
"""Print the beginning of the Pico le Croco response
"""
print(PICO_BEGIN + getpad() + ".")
def response_end():
"""Print the end of the clippy response
"""
+3
View File
@@ -0,0 +1,3 @@
from .main import run # pylint: disable=wrong-import-position
run()
File renamed without changes.
File diff suppressed because it is too large. Load diff
File renamed without changes.
+13
View File
@@ -0,0 +1,13 @@
from typing import Iterable, Callable
class R2AiEnv(dict):
def __init__(self):
self._callbacks = {}
def add_callback(self, key: str, callback: Callable):
self._callbacks[key] = callback
def __setitem__(self, __key, __value) -> None:
if __key in self._callbacks:
self._callbacks[__key](__value)
return super().__setitem__(__key, __value)
+80 -62
View File
@@ -5,13 +5,18 @@ import builtins
import requests
import json
import traceback
from utils import syscmdstr
have_chrome = True
try:
import chromadb
except Exception:
have_chrome = False
from unidecode import unidecode
import sys
from r2ai import LOGGER
try:
from .utils import slurp
from .utils import slurp, syscmdstr
from .const import R2AI_HISTFILE
except:
except Exception:
from utils import slurp
R2AI_HISTFILE = "/dev/null"
@@ -24,7 +29,7 @@ MASTODON_KEY = ""
try:
if "HOME" in os.environ:
MASTODON_KEY = slurp(os.environ["HOME"] + "/.r2ai.mastodon-key").strip()
except:
except Exception:
pass
MASTODON_INSTANCE = "mastodont.cat"
if "MASTODON_INSTANCE" in os.environ:
@@ -189,7 +194,7 @@ def filter_line(line):
try:
int(b)
continue
except:
except Exception:
pass
if len(b) > 0:
words.append(b)
@@ -216,60 +221,71 @@ def vectordb_search2(text, keywords, use_mastodon):
if use_mastodon:
print ("[r2ai] Searching in Mastodon", text)
lines = mastodon_lines(text, keywords, True)
# print("LINES", lines)
for line in lines:
# print("SAVE", line)
vectordb_instance.save(line, {"url":text})
vdb_add(f"{line} file={file}, url={url}")
idname=nextid()
if have_vectordb is True and vectordb_instance is not None:
res = []
text = "trump"
res = {}
try:
res = vectordb_instance.search(text, top_n=MAXMATCHES, unique=True, batch_results="diverse")
except:
try:
res = vectordb_instance.search(text, top_n=MAXMATCHES)
except:
traceback.print_exc()
pass
for r in res:
if "distance" in r:
# print("distance", r["distance"])
if r['distance'] < 1:
result.append(r["chunk"])
else:
# when mprt is not available we cant find the distance
result.append(r["chunk"])
#print(result)
res = vectordb_instance.query(query_texts=text)
except Exception:
traceback.print_exc()
pass
if res["documents"] is not None:
for docs in res["documents"]:
for doc in docs:
result.append(doc)
return sorted(set(result))
client = None
def vectordb_init():
global have_vectordb, client, vectordb_instance
if have_vectordb is False:
return
if vectordb_instance is not None:
return
try:
have_vectordb = True
except Exception:
print("To better data index use: pip install chromadb")
return
# try:
# vectordb_instance = vectordb.Memory(embeddings="normal") # normal or fast
# except Exception:
# vectordb_instance = vectordb.Memory() # normal or fast
# vectordb_instance = vectordb.Memory() # normal or fast
client = chromadb.Client() # normal or fast
try:
vectordb_instance = client.delete_collection("r2ai")
except Exception:
pass
vectordb_instance = client.create_collection("r2ai")
if vectordb_instance is not None:
vdb_add("radare2 is a free reverse engineering tool written by pancake, aka Sergi Alvarez i Capilla. The project started in 2006 as a tool for domestic computer forensics in order to recover some deleted files and it continued the development adding new features like debugging, disassembler, decompiler, code analysis, advanced filesystem capabilities and integration with tons of tools like Frida, Radius, Ghidra, etc") # dummy entry
def memorize(prompt, content):
global have_vectordb, vectordb_instance
if have_vectordb is False:
print("LEAVING")
builtins.print("no vdb found")
return
if vectordb_instance is not None:
vectordb_init()
if vectordb_instance is None:
return
try:
import vectordb
have_vectordb = True
except Exception as e:
os.system("python -m spacy download en_core_web_sm")
try:
import vectordb
have_vectordb = True
except:
have_vectordb = False
print("To better data index use:")
print(" pip install vectordb2")
print("On macOS you'll need to also do this:")
print(" python -m pip install spacy")
print(" python -m spacy download en_core_web_sm")
return
try:
vectordb_instance = vectordb.Memory(embeddings="best") # normal or fast
except:
vectordb_instance = vectordb.Memory() # normal or fast
if vectordb_instance is not None:
vectordb_instance.save("radare2 is a free reverse engineering tool written by pancake, aka Sergi Alvarez i Capilla. The project started in 2006 as a tool for domestic computer forensics in order to recover some deleted files and it continued the development adding new features like debugging, disassembler, decompiler, code analysis, advanced filesystem capabilities and integration with tons of tools like Frida, Radius, Ghidra, etc", {"url":"."}) # dummy entry
msg = f"{prompt} = {content}"
bs = 1024
while msg != "":
bob = msg[0:bs]
# bob = f"{prompt}={bob}"
vdb_add(bob)
msg = msg[bs:]
g_id = 0
def nextid():
global g_id
g_id = g_id + 1
return f"id{g_id}"
def vectordb_search(text, keywords, source_files, use_mastodon, use_debug):
global have_vectordb, vectordb_instance
@@ -290,17 +306,15 @@ def vectordb_search(text, keywords, source_files, use_mastodon, use_debug):
continue
lines = smart_slurp(file).splitlines()
for line in lines:
# vectordb_instance.save(line)
vectordb_instance.save(line, {"url":file}) #, "url": file})
vdb_add(f"{line} file={file}")
saved = saved + 1
if use_mastodon:
lines = mastodon_lines(text, None, True)
for line in lines:
saved = saved + 1
vectordb_instance.save(line, {"url":text})
vdb_add(f"{line} file={file}, url={url}")
if saved == 0:
print("[r2ai] Nothing indexed")
vectordb_instance.save("", {})
print("[r2ai] Nothing was indexed")
else:
print("[r2ai] VectorDB index done")
return vectordb_search2(text, keywords, use_mastodon)
@@ -327,8 +341,7 @@ class compute_rarity():
else:
self.words[a] = 1
def pull_realtime_lines(self, text, keywords, use_vectordb):
if self.env["debug"] == "true":
print(f"Pulling from mastodon {text}")
LOGGER.debug(f"Pulling from mastodon {text}")
return mastodon_lines(text, keywords, use_vectordb)
def find_matches(self, text, keywords):
@@ -395,7 +408,7 @@ def find_sources(srcdir):
files = []
try:
files = os.walk(srcdir)
except:
except Exception:
return []
res = []
for f in files:
@@ -421,28 +434,33 @@ def source_files(datadir, use_hist):
files.append(R2AI_HISTFILE)
return files
def vdb_add(data):
global have_vectordb, vectordb_instance
vectordb_init()
if vectordb_instance is not None:
vectordb_instance.add(ids=[nextid()], documents=[data])
def find_wikit(text, keywords):
print("wikit")
global have_vectordb, vectordb_instance
vectordb_init()
if vectordb_instance is None:
print("vdb not initialized")
print("vdb not initialized", file=sys.stderr)
return
if keywords is not None:
for kw in keywords:
print("wikit " + kw)
res = syscmdstr("wikit -a " + kw)
if len(res) > 20:
vectordb_instance.save(res, {"url":kw})
vdb_add(f"{res} from url={kw}")
words = filter_line(text)
for kw in words:
print("wikit " + kw)
res = syscmdstr("wikit -a " + kw)
if len(res) > 20:
vectordb_instance.save(res, {"keyword":kw})
vdb_add(f"{res} from url={kw}")
res = syscmdstr("wikit -a '" + " ".join(words) + "'")
if len(res) > 20:
vectordb_instance.save(res, {"keyword":kw})
vdb_add(f"{res} from keyword={kw}")
def reset():
global vectordb_instance
+579
View File
@@ -0,0 +1,579 @@
import builtins
import re
import os
import sys
import traceback
import json
import platform
have_local = True
import getpass
try:
import tokentrim
except Exception:
have_local = False
from rich.rule import Rule
from signal import signal, SIGINT
from .env import R2AiEnv
from .large import Large
from .utils import merge_deltas
from .message_block import MessageBlock
from .code_block import CodeBlock
from .backend import kobaldcpp
from .models import get_hf_llm, new_get_hf_llm, get_default_model
from .voice import tts
from .const import R2AI_HOMEDIR
from . import LOGGER, logging
from .web import stop_http_server, server_running
from .progress import progress_bar
file_dir = os.path.dirname(__file__)
sys.path.append(file_dir)
import index
r2clippy = False
have_rlang = False
try:
import r2lang
have_rlang = True
print = r2lang.print
r2clippy = True
except Exception:
pass
Ginterrupted = False
def signal_handler(sig, frame):
global Ginterrupted
if Ginterrupted:
stop_http_server(force=True) # kill if in background
sys.exit(0) # throws exception
Ginterrupted = True
print("^C 0", file=sys.stderr)
stop_http_server(force=False)
signal(SIGINT, signal_handler)
def exception_handler(self, sig, frame):
global Ginterrupted
traceback.print_exc()
if Ginterrupted:
sys.exit(0) # throws exception
Ginterrupted = True
print("^C 1", file=sys.stderr)
# sys.excepthook = exception_handler
def incodeblock(msg):
return "content" in msg and msg["content"].count("```") % 2 == 1
def r2eval(m):
if "$(" in m and have_rlang:
def evaluate_expression(match):
expression = match.group(1)
try:
result = r2lang.cmd(expression)
return result
except Exception as e:
return f"Error: {e}"
return re.sub(r'\$\((.*?)\)', evaluate_expression, m)
return m
from utils import syscmdstr
def ddg(m):
print("[R2AI] Crawling the web with ddg and curl+sed")
m = m.replace("'", "")
res = syscmdstr(f"cd examples; ./scrap-ddgweb.sh '{m}'")
return f"Considering:\n```{res}\n```\n"
def is_litellm_model(model):
import warnings
warnings.filterwarnings('ignore')
import litellm
provider = None
model_name = None
if model.startswith ("/"):
return False
provider = None
if "/" in model:
provider = model.split("/")[0]
elif ":" in model:
provider = model.split(":")[0]
if provider in litellm.LITELLM_CHAT_PROVIDERS:
return True
return False
class Interpreter:
def __init__(self):
self.logger = LOGGER
self.mistral = None
self.messages = []
self.terminator = "</s>" ## taken from the model using the llama api
self.api_key = None # openai?
self.print = None
self.auto_run = False
self.model = get_default_model()
self.last_model = ""
self.env = R2AiEnv()
self.system_message = ""
self.env["llm.model"] = self.model ## TODO: dup. must get rid of self.model
self.env["llm.gpu"] = "true"
self.env["llm.layers"] = "-1"
self.env["llm.window"] = "32768" # "4096" # context_window
self.env["llm.maxtokens"] = "4096" # "1750"
self.env["llm.maxmsglen"] = "8096" # "1750"
self.env["llm.temperature"] = "0.002"
self.env["llm.repeat_penalty"] = "1.0"
self.env["llm.top_p"] = "0.95"
self.env["llm.top_k"] = "50"
self.env["user.name"] = "" # TODO auto fill?
self.env["user.os"] = ""
self.env["user.arch"] = ""
self.env["user.cwd"] = ""
self.env["user.editor"] = ""
self.env["user.plugins"] = f"{R2AI_HOMEDIR}/plugins"
self.env["voice.lang"] = "en"
self.env["voice.model"] = "base"
self.env["data.use"] = "false"
self.env["data.path"] = f"{R2AI_HOMEDIR}/doc/data"
self.env["data.local"] = "false"
self.env["data.wikit"] = "false"
self.env["data.ddg"] = "false"
self.env["data.mastodon"] = "false"
self.env["data.vectordb"] = "false"
self.env["data.hist"] = "false"
self.env["key.mastodon"] = ""
self.env["http.port"] = "8080"
self.env["http.tabby"] = "false"
self.env["http.path"] = ""
self.env["http.verbose"] = "true" # not used yet
self.env["http.chatctx"] = "false"
self.env["debug_level"] = os.getenv("R2AI_LOG", "2")
if have_rlang:
self.env["chat.live"] = "false"
else:
self.env["chat.live"] = "true"
# self.env["chat.model"] = "" # TODO
self.env["chat.trim"] = "false"
self.env["chat.voice"] = "false"
self.env["chat.bubble"] = "false"
self.env["chat.reply"] = "false"
self.env["chat.code"] = "true"
self.env["chat.rawdog"] = "false"
self.env["chat.reasoning_effort"] = "high"
self.env["chat.stream"] = "true"
self.env["chat.show_cost"] = "false"
self.env["chat.drop_params"] = "false"
self.env["chat.openai_store"] = "false"
self.env["auto.max_runs"] = "100"
self.env["auto.hide_tool_output"] = "false"
self.env["auto.init_commands"] = "aaa;iI;afl"
self.env["auto.ask_to_execute"] = "true" # ask to run tools such as r2cmd, run_python etc
self.env.add_callback("debug_level", lambda val: LOGGER.setLevel(int(val) * 10))
# No active block to start
# (blocks are visual representation of messages on the terminal)
self.active_block = None
# Note: While Open Interpreter can use Llama, we will prioritize gpt-4.
# gpt-4 is faster, smarter, can call functions, and is all-around easier to use.
# This makes gpt-4 better aligned with Open Interpreters priority to be easy to use.
self.llama_instance = None
self.large = Large(self)
def get_info_for_system_message(self):
"""Gets relevent information for the system message."""
info = ""
# Add user info
getpass.getuser()
os.getcwd()
platform.system()
# info += f"[User Info]\nName: {username}\nCWD: {current_working_directory}\nOS: {operating_system}"
return info
def reset(self):
"""Resets the interpreter."""
self.messages = []
def load(self, messages):
self.messages = messages
def save(self, f):
json.dumps(self.messages, f, indent=2)
def handle_undo(self, arguments):
"""Remove all messages after the most recent user entry (and the entry itself).
Therefore user can jump back to the latest point of conversation.
Also gives a visual representation of the messages removed."""
if len(self.messages) == 0:
return
# Find the index of the last 'role': 'user' entry
last_user_index = None
for i, message in enumerate(self.messages):
if message.get('role') == 'user':
last_user_index = i
removed_messages = []
# Remove all messages after the last 'role': 'user'
if last_user_index is not None:
removed_messages = self.messages[last_user_index:]
self.messages = self.messages[:last_user_index]
# Print out a preview of what messages were removed.
for message in removed_messages:
if message.get("content"):
print(f"**Removed message:** `\"{message['content'][:30]}...\"`")
elif 'function_call' in message:
print(f"**Removed codeblock**") # TODO: Could add preview of code removed here.
# reimplement using nltk or index.match
def keywords_ai(self, text):
# kws = self.keywords_ai("who is the author of radare?") => "author,radare2"
words = []
mmname = "TheBloke/Mistral-7B-Instruct-v0.2-GGUF"
ctxwindow = int(self.env["llm.window"])
mm = new_get_hf_llm(self, mmname, ctxwindow)
msg = f"Considering the sentence \"{text}\" as input, Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
msg = f"Take \"{text}\" as input, and extract the keywords and combination of keywords to make a search online, the output must be a comma separated list" #Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
response = mm(msg, stream=False, temperature=0.1, stop="</s>", max_tokens=1750)
self.logger.debug(response)
text0 = response["choices"][0]["text"]
text0 = text0.replace('"', ",")
if text0.startswith("."):
text0 = text0[1:].strip()
try:
text0 = text0.split(":")[1].strip()
except Exception:
pass
# print(text0)
mm = None
return [word.strip() for word in text0.split(',')]
@progress_bar("Thinking", color="yellow")
def chat(self, message=None):
global print
global Ginterrupted
if self.print is not None:
print = self.print
if self.last_model != self.model:
# self.llama_instance = None
self.last_model = self.model
if not message and self.env["chat.code"] == "true":
self.end_active_block()
self.logger.error("Missing message")
return
if self.env["data.use"] == "true":
use_hist = self.env["data.hist"] == "true"
use_wikit = self.env["data.wikit"] == "true"
use_mastodon = self.env["data.mastodon"] == "true"
use_vectordb = self.env["data.vectordb"] == "true"
use_debug = LOGGER.level == logging.DEBUG
datadir = None
keywords = None
if use_mastodon:
keywords = self.keywords_ai(message)
if self.env["data.local"] == "true":
datadir = self.env["data.path"]
if self.env["data.ddg"] == "true":
results = ddg(message)
if use_vectordb:
index.memorize(message, results)
else:
self.messages.append({"role": "hint", "content": results})
matches = index.match(message, keywords, datadir, use_hist, use_mastodon, use_debug, use_wikit, use_vectordb)
if not matches:
matches = []
if len(matches) > 0:
for m in matches:
self.logger.debug("HINT: " + m)
self.messages.append({"role": "hint", "content": r2eval(m)})
self.logger.debug(message)
# print(message)
# Local model -- TODO: assume local models don't have : in the name?
if (
not self.model.startswith("openai:") and
not self.model.startswith("openapi:") and
not self.model.startswith("google:") and
not self.model.startswith("kobaldcpp") and
not self.model.startswith("bedrock:") and
not is_litellm_model(self.model)
):
self.logger = LOGGER.getChild(f"local:{self.model}")
# Find or install Code-Llama
try:
ctxwindow = int(self.env["llm.window"])
self.llama_instance = new_get_hf_llm(self, self.model, ctxwindow)
if self.llama_instance is None:
self.logger.error("Cannot find model " + self.model)
return
except Exception:
traceback.print_exc()
# If it was, we respond non-interactively
self.messages.append({"role": "user", "content": message})
response = None
try:
response = self.respond()
self.clear_hints()
except Exception:
if Ginterrupted:
Ginterrupted = False
else:
traceback.print_exc()
self.end_active_block()
return response
def end_active_block(self):
# if self.env["chat.code"] == "false":
# return
if self.active_block:
self.active_block.end()
self.active_block = None
def environment(self):
kvs = ""
if self.env["user.name"] != "":
kvs += "Name: " + self.env["user.name"] + "\n"
if self.env["user.os"] != "":
kvs += "OS: " + self.env["user.os"] + "\n"
if self.env["user.cwd"] != "":
kvs += "CWD: " + self.env["user.cwd"] + "\n"
if self.env["user.editor"] != "":
kvs += "EDITOR: " + self.env["user.editor"] + "\n"
if self.env["user.arch"] != "":
kvs += "ARCH: " + self.env["user.arch"] + "\n"
# info += f"[User Info]\nName: {username}\nCWD: {current_working_directory}\nOS: {operating_system}"
if kvs != "":
return "[User Info]\n" + kvs
return ""
def clear_hints(self):
res = []
for msg in self.messages:
if "role" in msg and msg["role"] != "hint":
res.append(msg)
self.messages = res
def respond(self):
global Ginterrupted
self.logger = LOGGER.getChild(self.model.split(":")[0])
maxtokens = int(self.env["llm.maxtokens"])
# Add relevant info to system_message
# (e.g. current working directory, username, os, etc.)
info = self.get_info_for_system_message()
# This is hacky, as we should have a different (minified) prompt for CodeLLama,
# but for now, to make the prompt shorter and remove "run_code" references, just get the first 2 lines:
self.system_message = "\n".join(self.system_message.split("\n")[:2])
# self.system_message += "\nOnly do what the user asks you to do, then ask what they'd like to do next."
system_message = self.system_message + "\n\n" + info
system_message += self.environment()
if self.env["chat.trim"] == "true":
## this stupid function is slow as hell and doesn not provides much goodies
## just ignore it by default
messages = tokentrim.trim(self.messages, max_tokens=maxtokens, system_message=system_message)
else:
messages = self.large.compress_messages(self.messages)
self.logger.debug(messages)
# builtins.print(prompt)
response = None
if self.auto_run:
from . import auto
import litellm
if self.env["chat.drop_params"] == "true":
litellm.drop_params = True
else:
litellm.drop_params = False
if(is_litellm_model(self.model)):
response = auto.chat(self)
else:
self.llama_instance = new_get_hf_llm(self, self.model, int(self.env["llm.window"]))
response = auto.chat(self)
return
elif self.model.startswith("kobaldcpp"):
if self.system_message != "":
message = f"Context:\n```\n{self.system_message}\n```\n"
else:
message = ""
#f"{Your name is r2ai, an assistant for radare2. User will ask about actions and you must respond with the radare2 command associated or the answer to the question. Be precise and concise when answering"
for m in messages:
role = m["role"]
content = m["content"]
if role == "user":
message += f"User: {content}\n"
elif role == "assistant":
message += f"AI: {content}\n"
response = ""
if ":" in self.model:
uri = self.model.split(":")[1:]
response = kobaldcpp.chat(message, ":".join(uri))
else:
response = kobaldcpp.chat(message)
if "content" in self.messages[-1]:
last_message = self.messages[-1]["content"]
if self.env["chat.reply"] == "true":
self.messages.append({"role": "assistant", "content": response})
print(response)
return
elif is_litellm_model(self.model):
# [
# {"role": "system", "content": "You are a poetic assistant, be creative."},
# {"role": "user", "content": "Compose a poem that explains the concept of recursion in programming."}
# ]
from litellm import completion as litellm_completion
completion = None
if self.model.startswith("ollama/"):
completion = litellm_completion(
model=self.model,
messages=self.messages,
drop_params=True,
max_completion_tokens=maxtokens,
temperature=float(self.env["llm.temperature"]),
top_p=float(self.env["llm.top_p"]),
)
elif self.model.startswith("mistral/"):
completion = litellm_completion(
drop_params=True,
model=self.model.replace(":", "/"),
messages=self.messages,
max_completion_tokens=maxtokens,
temperature=float(self.env["llm.temperature"]),
top_p=float(self.env["llm.top_p"]),
)
else:
completion = litellm_completion(
model=self.model.replace(":", "/"),
messages=self.messages,
max_completion_tokens=maxtokens,
temperature=float(self.env["llm.temperature"]),
top_p=float(self.env["llm.top_p"]),
)
response = completion.choices[0].message.content
if "content" in self.messages[-1]:
last_message = self.messages[-1]["content"]
if self.env["chat.reply"] == "true":
self.messages.append({"role": "assistant", "content": response})
print(response)
return response
else:
# non-openai aka local-llama model
if self.llama_instance == None:
self.logger.critical("Llama is not instantiated")
return
try:
if type(self.terminator).__name__ == "list":
terminator = self.terminator
else:
terminator = [self.terminator]
chat_args = {
"stream": True,
"temperature": float(self.env["llm.temperature"]),
"repeat_penalty": float(self.env["llm.repeat_penalty"]),
"top_p": float(self.env["llm.top_p"]),
"top_k": int(self.env["llm.top_k"]),
"stop": terminator,
"max_tokens": maxtokens
}
if self.env["chat.rawdog"] == "true":
from .completion import messages_to_prompt
allmsg = messages.copy()
allmsg.insert(0, ChatMessage(role=MessageRole.SYSTEM, content=self.system_message))
prompt = messages_to_prompt(self, allmsg)
response = self.llama_instance(prompt, **chat_args)
else:
all_messages = messages.copy()
all_messages.insert(0, {"role": "system", "content": self.system_message})
response = self.llama_instance.create_chat_completion(all_messages, **chat_args)
except Exception as err:
traceback.print_exc()
print(Exception, err)
if Ginterrupted:
Ginterrupted = False
return
if response is None:
print("No response")
ctxwindow = int(self.env["llm.window"])
self.llama_instance = new_get_hf_llm(self, self.model, ctxwindow)
return
# Initialize message, function call trackers, and active block
self.messages.append({})
in_function_call = False
self.active_block = MessageBlock()
for chunk in response:
if Ginterrupted:
Ginterrupted = False
break
text = ''
delta = None
if self.env["chat.rawdog"] == "true":
if "content" not in messages[-1]:
text = chunk["choices"][0]['text'].capitalize()
messages[-1] = { "content": '', "role": "assistant" }
else:
text = chunk["choices"][0]['text']
messages[-1] = merge_deltas(messages[-1], {"content": text})
else:
if "content" not in messages[-1]:
if "text" in chunk["choices"][0]['delta']:
text = chunk["choices"][0]['delta']["content"]
messages[-1] = { "content": text, "role": "assistant" }
delta = {"content": text}
elif "content" in chunk['choices'][0]['delta']:
delta = {"content": chunk['choices'][0]['delta']['content']}
# Accumulate deltas into the last message in messages
if delta:
self.messages[-1] = merge_deltas(self.messages[-1], delta)
if self.env["chat.live"] != "true":
continue
flushed = False
if self.env["chat.code"] == "true":
if incodeblock(self.messages[-1]):
if in_function_call == False:
in_function_call = True
self.active_block.update_from_message(self.messages[-1])
self.end_active_block()
# flushed = True
self.active_block = CodeBlock()
else:
if in_function_call == True:
in_function_call = False
self.end_active_block()
flushed = True
self.active_block = MessageBlock()
if self.env["chat.live"] == "true": # and self.env["chat.code"] == "true":
self.active_block.update_from_message(self.messages[-1])
if flushed:
self.messages[-1]["content"] = ""
continue # end of for loop
self.end_active_block()
self.active_block = None
output_text = ""
if len(self.messages) > 0 and "content" in self.messages[-1]:
output_text = self.messages[-1]["content"].strip()
if self.env["chat.reply"] == "true":
self.messages.append({"role": "assistant", "content": output_text})
if self.env["chat.voice"] == "true":
tts("(assistant)", output_text, self.env["voice.lang"])
elif self.env["chat.live"] != "true":
try:
r2lang.print(output_text)
except Exception:
print(output_text)
# print(str(self.messages))
+7 -6
View File
@@ -1,4 +1,5 @@
from .models import get_hf_llm, new_get_hf_llm, get_default_model
from . import LOGGER
import re
import json
@@ -10,6 +11,7 @@ class Large:
self.maxtokens = 5000
# self.model = "TheBloke/Mistral-7B-Instruct-v0.2-GGUF"
self.model = "FaradayDotDev/llama-3-8b-Instruct-GGUF"
self.logger = LOGGER.getChild(f"large[{self.model}]")
if ai is not None:
self.ai = ai
else:
@@ -29,7 +31,7 @@ class Large:
def compress_text(self, msg):
if self.mistral == None:
self.mistral = new_get_hf_llm(self.ai, self.model, False, self.window)
self.mistral = new_get_hf_llm(self.ai, self.model, self.window)
# q = f"Rewrite this code into shorter pseudocode (less than 500 tokens). keep the comments and essential logic:\n```\n{msg}\n```\n"
#q = f"Rewrite this code into shorter pseudocode (less than 200 tokens). keep the relevant comments and essential logic:\n```\n{msg}\n```\n"
q = f"Resumen y responde SOLO la información relevante del siguiente texto:\n{msg}"
@@ -58,19 +60,18 @@ class Large:
# kws = self.keywords_ai("who is the author of radare?") => "author,radare2"
words = []
ctxwindow = int(self.ai.env["llm.window"])
mm = new_get_hf_llm(self.ai, self.model, False, ctxwindow)
mm = new_get_hf_llm(self.ai, self.model, ctxwindow)
msg = f"Considering the sentence \"{text}\" as input, Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
msg = f"Take \"{text}\" as input, and extract the keywords and combination of keywords to make a search online, the output must be a comma separated list" #Take the KEYWORDS or combination of TWO words from the given text and respond ONLY a comma separated list of the most relevant words. DO NOT introduce your response, ONLY show the words"
response = mm(msg, stream=False, temperature=0.001, stop="</s>", max_tokens=1750)
if self.ai.env["debug"] == "true":
print("KWSPLITRESPONSE", response)
self.logger.debug(response)
text0 = response["choices"][0]["text"]
text0 = text0.replace('"', ",")
if text0.startswith("."):
text0 = text0[1:].strip()
try:
text0 = text0.split(":")[1].strip()
except:
except Exception:
pass
# print(text0)
mm = None
@@ -111,7 +112,7 @@ class Large:
words = []
if self.mistral == None:
ctxwindow = int(self.ai.env["llm.window"])
self.mistral = new_get_hf_llm(self.ai, self.model, False, ctxwindow)
self.mistral = new_get_hf_llm(self.ai, self.model, ctxwindow)
# q = f"Rewrite this code into shorter pseudocode (less than 500 tokens). keep the comments and essential logic:\n```\n{msg}\n```\n"
q = f"Rewrite this code into shorter pseudocode (less than 200 tokens). keep the relevant comments and essential logic:\n```\n{msg}\n```\n"
response = self.mistral(q, stream=False, temperature=0.1, stop="</s>", max_tokens=4096)
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
import os
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
import sys
import builtins
import traceback
import appdirs
import argparse
import litellm
from r2ai.repl import r2ai_singleton
from r2ai.utils import slurp
from r2ai.repl import runline, r2ai_repl, help_message
from r2ai.pipe import open_r2, get_r2_inst
from r2ai.const import R2AI_RCFILE
OPENAI_KEY = ""
HAVE_RLANG = False
HAVE_R2PIPE = False
RCFILE_LOADED = False
def r2ai_rlang_plugin(unused_but_required_argument):
ai = r2ai_singleton()
def _call(s):
if not s.startswith("r2ai"):
return False
try:
run_rcfile_once(ai)
if len(s) == 4:
builtins.print(help_message)
else:
usertext = s[4:].strip()
runline(ai, usertext)
except Exception as e:
builtins.print(e)
traceback.print_exc()
return True
return {
"name": "r2ai",
"license": "MIT",
"desc": "run llama language models inside r2",
"call": _call,
}
# TODO: see repl.run_script as replacement
def run_rcfile(ai):
try:
lines = slurp(R2AI_RCFILE)
for line in lines.split("\n"):
if line.strip() != "":
if ai is None:
ai = r2ai_singleton()
runline(ai, line)
except Exception:
pass
if ai is None:
ai = r2ai_singleton()
def run_rcfile_once(ai):
global RCFILE_LOADED
if not RCFILE_LOADED:
run_rcfile(ai)
RCFILE_LOADED = True
def main(args, commands, dorepl=True):
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
try:
r2aihome = os.path.dirname(os.path.realpath(__file__))
sys.path.append(r2aihome)
# if available
sys.path.append(
os.path.join(r2aihome, "..", "vectordb")
)
except Exception:
traceback.print_exc()
home_dir = os.path.expanduser("~")
# create symlink if it doesnt exist
try:
dst = os.path.join(home_dir, ".r2ai.models")
udd = appdirs.user_data_dir("r2ai")
src = os.path.join(udd, "models")
if not os.path.exists(dst):
os.symlink(src, dst)
except Exception:
traceback.print_exc()
r2_openai_file = os.path.join(home_dir, ".r2ai.openai-key")
if os.path.isfile(r2_openai_file):
apikey = slurp(r2_openai_file).strip()
os.environ["OPENAI_API_KEY"] = apikey
print("[R2AI] OpenAI API key loaded from ~/.r2ai.openai-key", file=sys.stderr)
r2_mistral_file = os.path.join(home_dir, ".r2ai.mistral-key")
if os.path.isfile(r2_mistral_file):
apikey = slurp(r2_mistral_file).strip()
os.environ["MISTRAL_API_KEY"] = apikey
print("[R2AI] Mistral API key loaded from ~/.r2ai.mistral-key", file=sys.stderr)
r2_gemini_file = os.path.join(home_dir, ".r2ai.gemini-key")
if os.path.isfile(r2_gemini_file):
apikey = slurp(r2_gemini_file).strip()
os.environ["GEMINI_API_KEY"] = apikey
print("[R2AI] Gemini API key loaded from ~/.r2ai.gemini-key", file=sys.stderr)
r2_anthropic_file = os.path.join(home_dir, ".r2ai.anthropic-key")
if os.path.isfile(r2_anthropic_file):
apikey = slurp(r2_anthropic_file).strip()
os.environ["ANTHROPIC_API_KEY"] = apikey
print("[R2AI] Anthropic API key loaded from ~/.r2ai.anthropic-key", file=sys.stderr)
ai = r2ai_singleton()
if "R2PIPE_IN" in os.environ:
pass
elif args.bin:
open_r2(vars(args)["bin"], flags=["-2"])
if commands is not None:
for c in commands:
if c.startswith("_"):
runline(ai, "-" + c[1:])
else:
runline(ai, c)
if dorepl:
r2ai_repl(ai)
def massage_args(args):
runrepl = True
if args.command is None:
args.command = []
if args.webserver:
args.command.append("-w")
if args.eval:
if args.eval == "default":
args.command.append("-e")
runrepl = False
else:
args.command.append(f"-e {args.eval}")
if args.port:
if args.port == "default":
runrepl = False
args.command.append("-e http.port")
else:
args.command.append(f"-e http.port={args.port}")
if args.model:
if args.model == "default":
args.command.append("-mm")
runrepl = False
else:
args.command.append(f"-m {args.model}")
return runrepl, args
def run():
parser = argparse.ArgumentParser()
parser.add_argument("bin", nargs="?", type=str)
parser.add_argument("-w", "--webserver", action="store_true",
help="Start the r2ai webserver. Same as r2ai -c=-w")
parser.add_argument("-p", "--port", type=str, nargs="?", const="default",
help="Change listen port number")
parser.add_argument("-e", "--eval", type=str, nargs="?", const="default",
help="Change configuration variable")
parser.add_argument("-m", "--model", type=str, nargs="?", const="default",
help="Select model name")
parser.add_argument("-c", "--command", action="append",
help="Command to be executed. Can be passed multiple times.")
runrepl, args = massage_args(parser.parse_args())
main(args, args.command, runrepl)
File renamed without changes.
+128 -38
View File
@@ -1,18 +1,14 @@
from .utils import slurp, dump
from huggingface_hub import hf_hub_download, login
from huggingface_hub import HfApi, list_repo_tree, get_paths_info
from typing import Dict, List, Union
import appdirs
import builtins
import inquirer
import json
import llama_cpp
import os
import shutil
import subprocess
import sys
import traceback
# DEFAULT_MODEL = "TheBloke/CodeLlama-34B-Instruct-GGUF"
# DEFAULT_MODEL = "TheBloke/llama2-7b-chat-codeCherryPop-qLoRA-GGUF"
# DEFAULT_MODEL = "-m TheBloke/dolphin-2_6-phi-2-GGUF"
@@ -28,28 +24,47 @@ def get_default_model():
fd.close()
if "default" in usermodels:
return usermodels["default"]
except:
except Exception:
pass
return DEFAULT_MODEL
def Markdown(x):
return x
return x
def mainmodels():
return """
Local:
Decai:
-m hugging-quants/Llama-3.2-3B-Instruct-Q8_0-GGUF
-m QuantFactory/granite-8b-code-instruct-4k-GGUF
-m cognitivecomputations/dolphin-2.9.3-mistral-nemo-12b-gguf
-m bartowski/Gemma-2-9B-It-SPPO-Iter3-GGUF
-m Qwen/Qwen2.5-Coder-7B-Instruct-GGUF
-m trufae/granite3-instruct-8b-gguf
Code:
-m unsloth/Llama-3.2-1B-Instruct-GGUF
-m mradermacher/starcoder-i1-GGUF
-m cognitivecomputations/dolphin-2.9.4-llama3.1-8b-gguf
-m FaradayDotDev/llama-3-8b-Instruct-GGUF
-m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
-m second-state/Mistral-Nemo-Instruct-2407-GGUF
-m bartowski/gemma-2-9b-it-GGUF
-m dagbs/dolphin-2.8-mistral-7b-v02-GGUF
-m meetkai/functionary-small-v2.2-GGUF
-m mradermacher/salamandra-7b-instruct-aina-hack-GGUF
Functionary:
-m meetkai/functionary-small-v3.2-GGUF
-m mradermacher/granite-20b-functioncalling-i1-GGUF
-m mradermacher/LocalAI-Llama3.1-8b-Function-Call-v0.3-i1-GGUF
Uncensored:
-m Orenguteng/Llama-3-8B-Lexi-Uncensored-GGUF
-m TheBloke/Unholy-v2-13B-GGUF
-m Undi95/Utopia-13B-GGUF
-m Ttimofeyka/MistralRP-Noromaid-NSFW-Mistral-7B-GGUF
-m bartowski/Phi-3.5-mini-instruct_Uncensored-GGUF
-m Orenguteng/Llama-3.1-8B-Lexi-Uncensored-V2-GGUF
-m LWDCLS/DarkIdol-Llama-3.1-8B-Instruct-1.2-Uncensored-GGUF-IQ-Imatrix-Request
-m Undi95/Llama-3-Unholy-8B-GGUF
-m mradermacher/Llama-3.2-1B-Instruct-Uncensored-GGUF
Remote:
-m openapi:http://localhost:5001
-m anthropic:claude-3-5-sonnet-20240620
-m openai:gpt-4
-m anthropic:claude-sonnet-5-5
-m bedrock:anthropic.claude-3-5-sonnet-20241022-v1
-m kobaldcpp:http://localhost:5001
"""
def models():
@@ -59,22 +74,22 @@ KobaldCpp:
-m kobaldcpp:http://localhost:5001
OpenAPI:
-m openapi:http://localhost:5001
-m openai:http://localhost:5001:gpt-4
OpenAI:
-m openai:gpt-3.5-turbo
-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-20240620
-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
-m groq:mixtral-8x7b-32768
Google:
-m google:gemini-1.0-pro
-m google:gemini-1.5-flash
-m google:gemini-1.5-pro-latest
Functionary:
-m meetkai/functionary-7b-v1.4-GGUF
@@ -131,9 +146,7 @@ Other:
def gpulayers(ai):
if "llm.gpu" in ai.env:
if ai.env["llm.gpu"] == "true":
print("[r2ai] Using GPU")
return -1
print("[r2ai] Using CPU")
return int(ai.env["llm.layers"])
return 0
def get_hf_llm(ai, repo_id, debug_mode, context_window):
@@ -143,9 +156,9 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
fd = open(r2ai_model_json)
usermodels = json.load(fd)
fd.close()
except:
except Exception:
usermodels = {}
pass
model_path = "" # slurp(r2ai_model_json)
if not repo_id:
repo_id = get_default_model()
@@ -154,9 +167,10 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
if usermodels is not None and repo_id in usermodels:
model_path = usermodels[repo_id]
# print(f"[r2ai] Using {r2ai_model_json} {model_path}")
return llama_cpp.Llama(model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
except:
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
except Exception:
traceback.print_exc()
print(f"Select {repo_id} model. See -M and -m flags", file=sys.stderr)
raw_models = list_gguf_files(repo_id)
if not raw_models:
@@ -242,6 +256,7 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
# Check if model was originally split
split_files = [model["filename"] for model in raw_models if selected_model in model["filename"]]
from huggingface_hub import hf_hub_download
if len(split_files) > 1:
# Download splits
for split_file in split_files:
@@ -253,18 +268,14 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
hf_hub_download(
repo_id=repo_id,
filename=split_file,
local_dir=default_path,
local_dir_use_symlinks=False,
resume_download=True)
local_dir=default_path)
# Combine and delete splits
actually_combine_files(default_path, selected_model, split_files)
else:
hf_hub_download(
repo_id=repo_id,
filename=selected_model,
local_dir=default_path,
local_dir_use_symlinks=False,
resume_download=True)
local_dir=default_path)
model_path = download_path
else:
@@ -272,7 +283,7 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
return None
try:
from llama_cpp import Llama
except:
except Exception:
if debug_mode:
traceback.print_exc()
# Ask for confirmation to install the required pip package
@@ -347,7 +358,73 @@ def get_hf_llm(ai, repo_id, debug_mode, context_window):
json.dump(usermodels, fd)
fd.close()
print("Saved")
return llama_cpp.Llama(model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
def list_downloaded_models():
try:
fd = open(r2ai_model_json)
usermodels = json.load(fd)
fd.close()
for m in usermodels:
if m.find("/") != -1:
M = m.ljust(40)
try:
size = round(os.path.getsize(usermodels[m]) / 1024 / 1024 / 1024, 2)
size = f"{size}".rjust(6)
print(f" {size}G {M}")
except Exception as e:
print(f" ??? {M}")
user_data_dir = appdirs.user_data_dir("r2ai")
models_path = os.path.join(user_data_dir, "models")
home_dir = os.path.expanduser("~")
sympath = os.path.join(home_dir, ".r2ai.models")
files = os.listdir(models_path)
for file in files:
if file.endswith(".gguf") == False:
continue
found = False
for m in usermodels:
last_part = usermodels[m].split("/")[-1]
if last_part == file:
found = True
if not found:
M = f"{sympath}/{file}"
size = round(os.path.getsize(M) / 1024 / 1024 / 1024, 2)
size = f"{size}".rjust(6)
print(f" {size}G {M}")
except Exception as e:
print(e)
return None
def delete_downloaded_model(name):
fd = None
try:
name = name.strip()
if not os.path.exists(name):
print("Cannot find " + name)
return None
if name.startswith("/"):
if name.endswith(".gguf"):
os.remove(name)
return None
print("Cant delete non-gguf files")
return None
fd = open(r2ai_model_json)
usermodels = json.load(fd)
fd.close()
fd = None
if name in usermodels:
modelpath = usermodels[name]
os.remove(modelpath)
del usermodels[name]
fd = open(r2ai_model_json, "w")
json.dump(usermodels, fd)
fd.close()
except Exception as e:
print(e)
if fd is not None:
fd.close()
return None
def set_default_model(repo_id):
usermodels = {"default": repo_id}
@@ -355,7 +432,7 @@ def set_default_model(repo_id):
fd = open(r2ai_model_json)
usermodels = json.load(fd)
fd.close()
except:
except Exception:
pass
usermodels["default"] = repo_id
# usermodels[repo_id] = model_path
@@ -383,6 +460,7 @@ def list_gguf_files(repo_id: str) -> List[Dict[str, Union[str, float]]]:
"""
try:
from huggingface_hub import HfApi
api = HfApi()
tree = list(api.list_repo_tree(repo_id))
files_info = [file for file in tree if file.path.endswith('.gguf')]
@@ -475,7 +553,8 @@ def enough_disk_space(size, path) -> bool:
return False
def new_get_hf_llm(ai, repo_id, debug_mode, context_window):
def new_get_hf_llm(ai, repo_id, context_window):
debug_mode = True if ai.env["debug_level"] == "1" else False
if repo_id.startswith("openai:") or repo_id.startswith("anthropic:") or repo_id.startswith("groq:") or repo_id.startswith("google:"):
return repo_id
if not os.path.exists(repo_id):
@@ -489,7 +568,7 @@ def new_get_hf_llm(ai, repo_id, debug_mode, context_window):
model_path = repo_id
try:
from llama_cpp import Llama
except:
except Exception:
if debug_mode:
traceback.print_exc()
# Ask for confirmation to install the required pip package
@@ -546,4 +625,15 @@ def new_get_hf_llm(ai, repo_id, debug_mode, context_window):
# Initialize and return Code-Llama
if not os.path.isfile(model_path):
print("Model is not a file")
return llama_cpp.Llama(model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
return get_llama_inst(repo_id, model_path=model_path, n_gpu_layers=gpulayers(ai), verbose=debug_mode, n_ctx=context_window)
def get_llama_inst(repo_id, **kwargs):
import llama_cpp
if 'functionary' in repo_id:
from llama_cpp.llama_tokenizer import LlamaHFTokenizer
kwargs['tokenizer'] = LlamaHFTokenizer.from_pretrained(repo_id)
filename = os.path.basename(kwargs.pop('model_path'))
kwargs['echo'] = True
return llama_cpp.Llama.from_pretrained(**kwargs, repo_id=repo_id, filename=filename)
return llama_cpp.Llama(**kwargs)
+223
View File
@@ -0,0 +1,223 @@
class IncompleteJSONParser:
def __init__(self, s):
self.s = s
self.len = len(s)
self.pos = 0
def parse(self):
self.skip_whitespace()
if self.pos >= self.len:
return None
char = self.s[self.pos]
if char == '{':
return self.parse_object()
elif char == '[':
return self.parse_array()
else:
return None # Top-level must be object or array
def parse_object(self):
if self.s[self.pos] != '{':
return None
self.pos += 1 # Skip '{'
self.skip_whitespace()
obj = {}
parsed_any = False # Flag to check if any key-value pair was parsed
while self.pos < self.len:
self.skip_whitespace()
if self.pos >= self.len:
break
if self.s[self.pos] == '}':
self.pos += 1
return obj if parsed_any else None
key = self.parse_string()
if key is None:
# Incomplete key, skip the rest
break
self.skip_whitespace()
if self.pos >= self.len or self.s[self.pos] != ':':
# Missing colon, skip this key
break
self.pos += 1 # Skip ':'
self.skip_whitespace()
value = self.parse_value()
if value is None:
# Incomplete value, skip this key-value pair
break
obj[key] = value
parsed_any = True
self.skip_whitespace()
if self.pos >= self.len:
break
if self.s[self.pos] == ',':
self.pos += 1
continue
elif self.s[self.pos] == '}':
self.pos += 1
return obj if parsed_any else None
else:
# Unexpected character, skip
break
# Auto-close the object
return obj if parsed_any else None
def parse_array(self):
if self.s[self.pos] != '[':
return None
self.pos += 1 # Skip '['
self.skip_whitespace()
array = []
parsed_any = False # Flag to check if any element was parsed
while self.pos < self.len:
self.skip_whitespace()
if self.pos >= self.len:
break
if self.s[self.pos] == ']':
self.pos += 1
return array if parsed_any else None
value = self.parse_value()
if value is None:
# Incomplete value, skip this element
break
array.append(value)
parsed_any = True
self.skip_whitespace()
if self.pos >= self.len:
break
if self.s[self.pos] == ',':
self.pos += 1
continue
elif self.s[self.pos] == ']':
self.pos += 1
return array if parsed_any else None
else:
# Unexpected character, skip
break
# Auto-close the array
return array if parsed_any else None
def parse_value(self):
self.skip_whitespace()
if self.pos >= self.len:
return None
char = self.s[self.pos]
if char == '"':
return self.parse_string()
elif char == '{':
return self.parse_object()
elif char == '[':
return self.parse_array()
elif char in '-0123456789':
return self.parse_number()
elif self.s.startswith('true', self.pos):
self.pos += 4
return True
elif self.s.startswith('false', self.pos):
self.pos += 5
return False
elif self.s.startswith('null', self.pos):
self.pos += 4
return None
else:
return None # Invalid value
def parse_string(self):
if self.s[self.pos] != '"':
return None
self.pos += 1 # Skip opening quote
result = ""
while self.pos < self.len:
char = self.s[self.pos]
if char == '\\':
if self.pos + 1 >= self.len:
result += '\\'
self.pos += 1
break # Incomplete escape, return what we have
self.pos += 1
escape_char = self.s[self.pos]
if escape_char == '"':
result += '"'
elif escape_char == '\\':
result += '\\'
elif escape_char == '/':
result += '/'
elif escape_char == 'b':
result += '\b'
elif escape_char == 'f':
result += '\f'
elif escape_char == 'n':
result += '\n'
elif escape_char == 'r':
result += '\r'
elif escape_char == 't':
result += '\t'
elif escape_char == 'u':
# Unicode escape
if self.pos + 4 >= self.len:
# Incomplete unicode escape
break
hex_digits = self.s[self.pos+1:self.pos+5]
try:
code_point = int(hex_digits, 16)
result += chr(code_point)
self.pos += 4
except ValueError:
break # Invalid unicode escape
else:
# Invalid escape character
result += '\\' + escape_char # Keep it as is
elif char == '"':
self.pos += 1 # Skip closing quote
return result
else:
result += char
self.pos += 1
# Return the partial string if incomplete
return result
def parse_number(self):
start = self.pos
if self.s[self.pos] == '-':
self.pos += 1
if self.pos >= self.len:
return None
if self.s[self.pos] == '0':
self.pos += 1
elif '1' <= self.s[self.pos] <= '9':
while self.pos < self.len and self.s[self.pos].isdigit():
self.pos += 1
else:
return None # Invalid number
if self.pos < self.len and self.s[self.pos] == '.':
self.pos += 1
if self.pos >= self.len or not self.s[self.pos].isdigit():
return None # Incomplete fraction
while self.pos < self.len and self.s[self.pos].isdigit():
self.pos += 1
if self.pos < self.len and self.s[self.pos] in 'eE':
self.pos += 1
if self.pos < self.len and self.s[self.pos] in '+-':
self.pos += 1
if self.pos >= self.len or not self.s[self.pos].isdigit():
return None # Incomplete exponent
while self.pos < self.len and self.s[self.pos].isdigit():
self.pos += 1
num_str = self.s[start:self.pos]
try:
if '.' in num_str or 'e' in num_str or 'E' in num_str:
return float(num_str)
else:
return int(num_str)
except ValueError:
return None # Invalid number
def skip_whitespace(self):
while self.pos < self.len and self.s[self.pos] in ' \t\n\r':
self.pos += 1
def parse_incomplete_json(s):
parser = IncompleteJSONParser(s)
result = parser.parse()
return result
+28 -8
View File
@@ -1,12 +1,17 @@
import os
import traceback
import r2pipe
have_rlang = False
r2lang = None
r2 = None
filename = None
class FakeLang:
def __init__(self, r2 = None):
self.r2 = r2
def ai(self, x):
try:
from r2ai.repl import r2ai_singleton, runline2
@@ -15,9 +20,10 @@ class FakeLang:
print("No global r2ai instance found")
return ""
return runline2(ai, x)
except:
except Exception:
traceback.print_exc()
return None
def cmd(self, x):
if self.r2 is None:
return ""
@@ -28,28 +34,42 @@ class FakeLang:
try:
import r2lang
have_rlang = True
except:
except Exception:
import r2pipe
try:
if r2pipe.in_r2():
r2lang = FakeLang(r2pipe.open())
r2lang.cmd("?V") # r2pipe throws only here
else:
raise Error("must spawn")
except:
raise Exception("must spawn")
except Exception:
try:
have_rlang = False
if os.environ.get('R2AI') is None:
ppid = os.getppid()
os.environ["R2AI"] = "1"
r2lang = FakeLang(r2pipe.open("/bin/ls"))
filename = "/bin/ls"
r2lang = FakeLang(r2pipe.open(filename))
else:
r2lang = FakeLang(None)
except:
except Exception:
print("Cannot instantiate this FakeLang class with r2pipe")
r2lang = FakeLang(None)
pass
def r2singleton():
global r2lang
global r2lang, r2
return r2lang or r2
def get_r2_inst():
return r2singleton()
def open_r2(file, flags=[]):
global r2, filename, r2lang
r2 = r2pipe.open(file, flags=flags)
r2lang = FakeLang(r2)
filename = file
return r2lang
def get_filename():
global filename
return filename
+47
View File
@@ -0,0 +1,47 @@
"""Entrypoint for the r2ai plugin and repl."""
import sys
import os
import builtins
import traceback
current_dir = os.path.dirname(os.path.realpath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.insert(0, parent_dir)
os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
try:
venv_dir = os.path.join(parent_dir, 'venv')
if os.path.exists(venv_dir):
site_packages = os.path.join(venv_dir, 'lib', 'python{}.{}'.format(*sys.version_info[:2]), 'site-packages')
if os.path.exists(site_packages):
sys.path.insert(0, site_packages)
except Exception:
pass
import r2lang
from r2ai.main import r2ai_singleton, run_rcfile_once, runline, help_message
def r2ai_rlang_plugin(unused_but_required_argument):
ai = r2ai_singleton()
def _call(s):
if not s.startswith("r2ai"):
return False
try:
run_rcfile_once(ai)
if len(s) == 4:
builtins.print(help_message)
else:
usertext = s[4:].strip()
runline(ai, usertext)
except Exception as e:
builtins.print(e)
traceback.print_exc()
return True
return {
"name": "r2ai",
"license": "MIT",
"desc": "run llama language models inside r2",
"call": _call,
}
r2lang.plugin("core", r2ai_rlang_plugin)
+100
View File
@@ -0,0 +1,100 @@
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn, Task
from rich.console import Console
from .web import server_running, server_in_background
from inspect import signature
from functools import wraps
def _support_total(sig, *args, **kwargs):
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
return "__progress_cls" in bound.arguments and "__progress_task" in bound.arguments
def progress_bar(text, color=None, total=None, infinite=False):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
sig = signature(func)
has_total = total is not None and _support_total(
sig, *args, **kwargs)
is_infinite = infinite or not has_total
# if server_running() and not server_in_background():
return func(*args, **kwargs)
with Progress(SpinnerColumn(), *Progress.get_default_columns(), console=Console(no_color=not bool(color)), transient=False) as p:
task_text = f"[{color}]{text}" if color else text
task = p.add_task(
task_text, total=None if is_infinite else total)
if has_total:
result = func(
*args,
**kwargs,
__progress_cls=p,
__progress_task=task)
else:
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# For consistency with the above
class ProgressBar:
def __init__(self, text, color=None, total=None, infinite=False) -> None:
self.text = text
self.color = color
self.total = total
self.infinite = infinite
self.progress: Progress = None
self.task: Task = None
def __enter__(self):
self.progress = Progress(
SpinnerColumn(),
*Progress.get_default_columns(),
console=Console(
no_color=not bool(
self.color)),
transient=False)
if self.color:
self.task = self.progress.add_task(
f"[{self.color}]{self.text}", total=None if self.infinite else self.total)
else:
self.task = self.progress.add_task(
f"{self.text}", total=None if self.infinite else self.total)
self.progress.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.progress:
self.progress.stop()
# from .progress import ProgressBar, progress_bar
# progress_bar is used as an decorator
# @progress_bar(Title, color="yellow")
# def foo():
# bar
#
#
# unlike in the class, the decorated functin can only use progressive progress only if
# __progress_cls and __progress_task are used as positional arguments. else it defaults to infinite
# @progress_bar("Title", color="yellow", total=100)
# def foo(a,b, __progress_cls=None, __progress_task=None):
# i = 1
# while True:
# progress_cls.update(p.task, advance=i)
# i+=1
# time.sleep(1)
#
#
# ProgressBar is made for consistency with the decorator
# import time
# with ProgressBar("Title", color="Yellow", total=50) as p:
# i = 0
# while True:
# p.progress.update(p.task, advance=i)
# i+=1
# time.sleep(1)
+145 -59
View File
@@ -1,6 +1,7 @@
import builtins
from .models import set_default_model, models, mainmodels
from .models import set_default_model, list_downloaded_models, delete_downloaded_model, models, mainmodels
from .utils import slurp
from .large import Large
import traceback
from .const import R2AI_HISTFILE, R2AI_HOMEDIR, R2AI_RCFILE, R2AI_USERDIR
from .web import start_http_server
@@ -9,36 +10,31 @@ from datetime import datetime
import sys
import os
from .tab import tab_init, tab_hist, tab_write, tab_evals
from .tab import tab_init, tab_hist, tab_write, tab_evals, tab_list
from .interpreter import Interpreter
from .pipe import have_rlang, r2lang, r2singleton
from r2ai import bubble, LOGGER
from .test import run_test
tab_init()
print_buffer = ""
ais = []
autoai = None
from .pipe import have_rlang, r2lang, r2singleton
r2 = r2singleton()
def r2ai_singleton():
global ais
if len(ais) == 0:
from .interpreter import Interpreter
ai = Interpreter()
ais.append(R2AI(ai))
return ais[0].ai
def r2_cmd(x):
global have_rlang, ai, r2, r2_file
have_rlang = True
res = x
if have_rlang:
oc = r2lang.cmd('e scr.color').strip()
r2lang.cmd('e scr.color=0')
res = r2lang.cmd(x)
r2lang.cmd('e scr.color=' + oc)
elif r2 is not None:
r2 = r2singleton()
res = None
if r2 is not None:
oc = r2.cmd('e scr.color').strip()
r2.cmd('e scr.color=0')
r2.cmd('e scr.color=3')
res = r2.cmd(x)
r2.cmd('e scr.color=' + oc)
return res
@@ -51,6 +47,7 @@ help_message = """Usage: r2ai [-option] ([query] | [script.py])
r2ai ?e [msg] echo a message
r2ai ?t [query] run an query and show it's timing
r2ai !ls run a system command
r2ai ! show prompt history
r2ai -a query with audio voice
r2ai -A enter the voice chat loop
r2ai -k clear the screen
@@ -61,21 +58,26 @@ help_message = """Usage: r2ai [-option] ([query] | [script.py])
r2ai -h show this help (same as ?)
r2ai -H ([var]) show path variables like it's done in r2 -H
r2ai -i [file] ([q]) load the file contents and prompt it with the given optional query
r2ai -j [query] convert the user prompt into a meaningful json
r2ai -m [file/repo] select model from huggingface repository or local file
r2ai -m-[repo/model] delete a local downloaded model (see -mm for listing them)
r2ai -mm list all downloaded models
r2ai -M shorter list of models
r2ai -MM list supported and most common models from hf
r2ai -n [num] select the nth language model
r2ai -q quit/exit/^C
r2ai -L show chat logs (See -Lj for json)
r2ai -L-[N] delete the last (or N last messages from the chat history)
r2ai -p1c0 enter the Pico le Croco assistant mode
r2ai -repl enter the repl (only when running via r2pipe)
r2ai -r [sysprompt] define the role of the conversation
r2ai -r2 enter the r2clippy assistant mode
r2ai -rf [doc/role/.f] load contents of a file to define the role
r2ai -R reset the chat conversation context
r2ai -t [temp] from 0.0001 to 10 your scale to randomness in my replies
r2ai -v show r2ai version (same as ?V)
r2ai -w ([port]) start webserver (curl -D hello http://localhost:8000)
r2ai -W ([port]) start webserver in background
r2ai -VV visual mode
r2ai -W ([port]) start webserver in background"""
def myprint(msg, file=None):
@@ -113,11 +115,10 @@ def runplugin(ai, arg):
for file in files:
if file.endswith(".py"):
print(file.replace(".py", ""))
except:
except Exception:
pass
def r2ai_version():
import sys
import llama_cpp
print("python: " + sys.version)
print("llama: " + llama_cpp.__version__)
@@ -147,14 +148,41 @@ def run_script(ai, script):
for line in lines.split("\n"):
if line.strip() != "":
runline(ai, line)
except:
except Exception:
pass
class R2AI:
def __init__(self,ai):
self.ai = ai
def cmd(x):
return runline2(self.ai, cmd)
def cmd(self, x):
return runline2(self.ai, x)
def slurp_until(endword):
text = ""
while True:
line = sys.stdin.readline()
if line.strip() == endword:
break
text += line
return text
def list_models():
print("ollama/llama3:latest")
print("ollama/granite-code:8b")
print("anthropic/claude-sonnet-5-5")
print("openai/gpt-4o")
print("mistral/mistral-tiny")
def set_model(model):
ai = ais[0].ai
if ai.model != model:
ai.llama_instance = None
ai.model = model
if autoai:
autoai.model = model
ai.env["llm.model"] = ai.model
set_default_model(ai.model)
def runline(ai, usertext):
global print
@@ -177,10 +205,17 @@ def runline(ai, usertext):
if usertext.startswith("-H"):
try:
return r2ai_vars(ai, usertext[2:].strip())
except:
except Exception:
traceback.print_exc()
if usertext.startswith("-VV"):
from ui.app import R2AIApp # pylint: disable=import-error
R2AIApp(ai=ai).run()
return
if usertext.startswith("?V") or usertext.startswith("-v"):
r2ai_version()
elif usertext.startswith("<<"):
newline = slurp_until(usertext[2:])
return runline(ai, newline)
elif usertext.startswith("?e"):
print(usertext[2:].strip())
elif usertext.startswith("?t"):
@@ -188,29 +223,40 @@ def runline(ai, usertext):
runline(ai, usertext[2:].strip())
tend = datetime.now()
print(tend - tstart)
elif usertext.startswith("?") or usertext.startswith("-h"):
elif usertext.startswith("?") or usertext.startswith("-h") or usertext.startswith("-?"):
print(help_message)
elif usertext.startswith("clear") or usertext.startswith("-k"):
print("\x1b[2J\x1b[0;0H\r")
if ai.messages:
ai.messages = []
if autoai and autoai.messages:
autoai.messages = []
elif usertext.startswith("-MM"):
print(models().strip())
elif usertext.startswith("-M"):
print(mainmodels().strip())
elif usertext.startswith("-mm"):
list_downloaded_models()
elif usertext.startswith("-m-"):
delete_downloaded_model(usertext[3:])
elif usertext.startswith("r2ai"):
print("ERROR: Cant run r2ai from r2ai, you must install the C plugin and run this command from radare2")
elif usertext == "-m?":
list_models()
elif usertext.startswith("-m"):
words = usertext.split(" ")
if len(words) > 1:
ai.model = words[1]
ai.env["llm.model"] = ai.model
set_default_model(ai.model)
set_model(words[1])
else:
print(ai.model)
elif usertext == "reset" or usertext.startswith("-R"):
ai.reset()
elif usertext.startswith("-t"):
if usertext == "-t":
print(ai.env["llm.temperature"])
else:
ai.env["llm.temperature"] = usertext[2:].strip()
if autoai:
autoai.reset()
elif usertext.startswith("-j"):
q = usertext[2:].strip()
query = f"Please respond using ONLY in JSON with the following fields if relevant: topic, array of key words, location, url, name, description, target, amounts and other details if necessary without providing any response. Question: \"{q}\""
ai.chat(query)
elif usertext == "-A":
from .voice import stt
ai.env["chat.voice"] = "true"
@@ -246,6 +292,13 @@ def runline(ai, usertext):
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
ai.env["chat.bubble"] = "true"
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/r2clippy.txt")
elif usertext == "-p1c0":
ai.env["data.use"] = "true"
ai.env["data.hist"] = "true"
ai.env["data.path"] = f"{R2AI_HOMEDIR}/doc/"
ai.env["chat.bubble"] = "true"
ai.env["chat.picolecroco"] = "true"
runline(ai, f"-rf {R2AI_HOMEDIR}/doc/role/picolecroco.txt")
elif usertext.startswith("-ed"):
editor = "vim" # Defaults to the only real editor
if ai.env["user.editor"] != "":
@@ -283,28 +336,36 @@ def runline(ai, usertext):
else:
try:
print(ai.env[k])
except:
except Exception:
print("Invalid config key", file=sys.stderr)
pass
elif usertext.startswith("-l"):
try:
l = Large()
l = Large(ai)
t = slurp("doc/samples/qcw.txt")
print(l.summarize_text(t))
except:
except Exception:
traceback.print_exc()
sys.exit(0)
elif usertext.startswith("-W"):
if len(usertext) > 2:
port = int(usertext[2:])
if port > 0:
runline2(ai, f"-e http.port={port}")
try:
port = int(usertext[2:])
if port > 0:
runline2(ai, f"-e http.port={port}")
except:
print("Use -w [port-number]")
return
start_http_server(ai, runline2, True)
elif usertext.startswith("-w"):
if len(usertext) > 2:
port = int(usertext[2:])
if port > 0:
runline2(ai, f"-e http.port={port}")
try:
port = int(usertext[2:])
if port > 0:
runline2(ai, f"-e http.port={port}")
except:
print("Use -w [port-number]")
return
start_http_server(ai, runline2, False)
elif usertext.startswith("-s"):
ai.runline2 = runline2
@@ -314,7 +375,7 @@ def runline(ai, usertext):
fname = usertext[3:].strip()
try:
ai.system_message = slurp(fname)
except:
except Exception:
print(f"Cannot open file {fname}", file=sys.stderr)
else:
print(ai.system_message)
@@ -328,13 +389,13 @@ def runline(ai, usertext):
amount = int(usertext[3:])
if amount < 1:
amount = 1
except:
except Exception:
amount = 1
try:
for i in range(amount):
ai.messages.pop() # delete user message
ai.messages.pop() # delete assistant message
except:
except Exception:
pass
elif usertext.startswith("-Lj"):
print(ai.messages)
@@ -347,7 +408,7 @@ def runline(ai, usertext):
try:
res = slurp(text)
ai.chat(res)
except:
except Exception:
print("Cannot load file", file=sys.stderr)
elif usertext.startswith("-i"):
text = usertext[2:].strip()
@@ -378,7 +439,6 @@ def runline(ai, usertext):
if index < len(ais):
ai = ais[index].ai
else:
from .interpreter import Interpreter
ai0 = Interpreter()
ai0.model = ai.model
ais.append(R2AI(ai0))
@@ -391,13 +451,33 @@ def runline(ai, usertext):
que = words[1]
else:
que = input("[Query]> ")
except:
except Exception:
print("")
return
tag = "```\n" # TEXT, INPUT ..
ai.chat(f"{que}:\n{tag}\n{res}\n{tag}\n")
elif usertext[0] == "!":
os.system(usertext[1:])
import shutil
columns, rows = shutil.get_terminal_size()
if len(usertext) == 1:
count = 0
for item in tab_list():
print(item, file=sys.stderr)
count = count + 1
if count == rows - 1:
print("-- More -- (q)")
count = 0
if input() == 'q':
break
else:
safe_int = lambda x: int(x) if str(x).isdigit() else 0
n = safe_int(usertext[1:])
if n > 0:
items = tab_list()
runline(ai, items[n - 1])
else:
os.system(usertext[1:])
elif usertext[0] == ".":
#if len(usertext) > 1 and usertext[1] == ".": # ".." - run user plugins
# runplugin(ai, usertext[2:].strip())
@@ -410,29 +490,32 @@ def runline(ai, usertext):
else:
for line in file.split("\n"):
runline(ai, line)
except Exception as e:
except Exception:
traceback.print_exc()
pass
elif usertext.startswith("' "):
if not autoai:
from .interpreter import Interpreter
autoai = Interpreter()
autoai.auto_run = True
autoai.env = ai.env
autoai.auto_run = True
autoai.chat(usertext[2:])
elif usertext[0] == ":":
if r2 is None:
print("r2 is not available", file=sys.stderr)
else:
builtins.print(r2_cmd(usertext[1:]))
elif usertext.startswith("--test"):
run_test(usertext[7:])
elif usertext.startswith("-"):
print("Unknown flag. See 'r2ai -h' for help", file=sys.stderr)
print(f"Unknown flag '{usertext}'. See 'r2ai -h' for help", file=sys.stderr)
else:
ai.chat(usertext)
def r2ai_repl(ai):
from r2ai import bubble
tab_evals(ai.env.keys())
oldoff = r2_cmd("s").strip()
oldoff = r2_cmd("?vx $$").strip()
if oldoff == "0x0" or oldoff == "":
oldoff = "0x00000000"
olivemode = ai.env["chat.live"]
@@ -442,9 +525,9 @@ def r2ai_repl(ai):
if os.name != "nt":
prompt = f"\001\x1b[33m\002{prompt}" # \001\x1b[0m\002"
if r2 is not None:
off = r2_cmd("s").strip()
off = r2_cmd("?vx $$").strip()
if off == "":
off = r2_cmd("s").strip()
off = r2_cmd("?vx $$").strip()
if len(off) > 5 and len(off) < 20:
oldoff = off
if ai.active_block is not None:
@@ -456,7 +539,7 @@ def r2ai_repl(ai):
builtins.print("\001\x1b[0m\002", end="")
except EOFError:
break
except:
except Exception:
traceback.print_exc()
break
try:
@@ -466,14 +549,17 @@ def r2ai_repl(ai):
break
else:
bubble.query(usertext)
bubble.response_begin()
if ai.env["chat.picolecroco"] == "true":
bubble.pico_begin()
else:
bubble.response_begin()
if runline(ai, usertext) == "q":
break
bubble.response_end()
else:
if runline(ai, usertext) == "q":
break
except:
except Exception:
traceback.print_exc()
continue
tab_write()
+42
View File
@@ -0,0 +1,42 @@
import itertools
import threading
import time
import sys
class Spinner:
def __init__(self, message="Loading...", delay=0.1):
self.spinner = itertools.cycle([
"\033[1;31m⠋\033[0m", "\033[1;32m⠙\033[0m", "\033[1;33m⠹\033[0m", "\033[1;34m⠸\033[0m",
"\033[1;35m⠼\033[0m", "\033[1;36m⠴\033[0m", "\033[1;37m⠦\033[0m", "\033[1;31m⠧\033[0m",
"\033[1;32m⠇\033[0m", "\033[1;33m⠏\033[0m"
])
self.message = message
self.delay = delay
self.running = False
self.thread = None
self.start_time = None
def start(self):
"""Start the spinner in a separate thread."""
self.running = True
self.start_time = time.time()
self.thread = threading.Thread(target=self._spin)
self.thread.start()
def _spin(self):
"""Spin the spinner while running is True."""
while self.running:
elapsed_time = time.time() - self.start_time
sys.stdout.write(f"\r{self.message} {next(self.spinner)} {elapsed_time:.1f}s")
sys.stdout.flush()
time.sleep(self.delay)
sys.stdout.write('\r' + ' ' * (len(self.message) + 20) + '\r') # Clear the line
sys.stdout.flush()
def stop(self):
"""Stop the spinner."""
self.running = False
if self.thread is not None:
self.thread.join()
spinner = Spinner("")
+27 -9
View File
@@ -1,4 +1,5 @@
import os
import sys
from .const import R2AI_HISTFILE, R2AI_HOMEDIR, R2AI_RCFILE, R2AI_USERDIR
from .models import models
@@ -67,7 +68,7 @@ class MyCompleter(object): # Custom completer
def display_matches(self, substitution, matches, longest_match_length):
line_buffer = readline.get_line_buffer()
columns = environ.get("COLUMNS", 80)
columns = os.environ.get("COLUMNS", 80)
print()
tpl = "{:<" + str(int(max(map(len, matches)) * 1.2)) + "}"
buffer = ""
@@ -99,11 +100,11 @@ try:
import readline
import rlcompleter
have_readline = True
except:
except Exception:
have_readline = True
pass # readline not available
def tab_hist(text):
def tab_hist():
if not have_readline:
print("Cannot find readline", file=sys.stderr)
return False
@@ -118,6 +119,17 @@ def tab_write():
return False
readline.write_history_file(R2AI_HISTFILE)
def tab_list():
global readline
if not have_readline:
return []
amount = readline.get_current_history_length()
res = []
for i in range(1, amount):
item = readline.get_history_item(i)
res.append(f"{i} {item}")
return res
def tab_init():
if not have_readline:
print("Cannot find readline", file=sys.stderr)
@@ -127,9 +139,15 @@ def tab_init():
readline.read_history_file(R2AI_HISTFILE)
except FileNotFoundError:
pass
readline.set_completer(completer.complete)
readline.set_completer_delims('\t\n;')
readline.set_completion_display_matches_hook(completer.display_matches)
#readline.parse_and_bind("bind -e")
readline.parse_and_bind('tab: complete') # linux tab (gnu readline)
readline.parse_and_bind("bind ^I rl_complete") ## macos tab (libedit)
except Exception:
pass
try:
readline.set_completer(completer.complete)
readline.set_completer_delims('\t\n;')
readline.set_completion_display_matches_hook(completer.display_matches)
if readline.__doc__.find("GNU") != -1:
readline.parse_and_bind('tab: complete')
else:
readline.parse_and_bind("bind ^I rl_complete")
except Exception as e:
pass
+37
View File
@@ -0,0 +1,37 @@
import builtins
from .tools import run_python, execute_binary, r2cmd
import subprocess
from .pipe import get_filename
import time
py_code = """
print('hello test')
"""
def run_test(args):
if not args or len(args) == 0:
res = run_python(py_code).strip()
print(f"run_python: {res}", len(res))
assert res == "hello test"
print("run_python: test passed")
r2cmd("o--;o /bin/ls")
res = execute_binary(args=["-d", "/etc"]).strip()
subp = subprocess.run(["/bin/ls", "-d", "/etc"], capture_output=True, text=True)
print("exec result", res)
print("subp result", subp.stdout)
assert ''.join(res).strip() == subp.stdout.strip()
print("execute_binary with args: test passed")
else:
cmd, *args = args.split(" ", 1)
if cmd == "get_filename":
builtins.print(get_filename())
elif cmd == "run_python":
builtins.print(f"--- args ---")
builtins.print(args)
builtins.print(f"--- end args ---")
builtins.print(f"--- result ---")
builtins.print(run_python(args[0]))
builtins.print(f"--- end result ---")
elif cmd == "r2cmd":
builtins.print(f"--- {args} ---")
builtins.print(r2cmd(args))
builtins.print("--- end ---")
+176
View File
@@ -0,0 +1,176 @@
from r2ai.pipe import get_r2_inst
import json
import builtins
import base64
from .pipe import get_filename
from . import LOGGER
import time
import sys
from io import StringIO
import subprocess
import os
is_plugin = False
try:
import r2lang
is_plugin = True
except Exception:
is_plugin = False
pass
def r2cmd(command: str):
"""
Run a r2 command and return the output
Parameters
----------
command: str
The r2 command to run
Returns
-------
dict
The output of the r2 command
"""
r2 = get_r2_inst()
r2.cmd('e scr.color=3')
if command.startswith('r2 '):
return "You are already in r2!"
cmd = '{"cmd":' + json.dumps(command) + '}'
res = r2.cmd(cmd)
try:
res = json.loads(res)
if 'error' in res and res['error'] is True:
error_message = res['error']
log_messages = '\n'.join(log['message'] for log in res.get('logs', []))
# return { 'type': 'error', 'output': log_messages }
return log_messages
return res['res']
except json.JSONDecodeError:
if type(res) == str:
spl = res.strip().split('\n')
if spl[-1].startswith('{"res":""'):
res = '\n'.join(spl[:-1])
return res
except Exception as e:
# return { 'type': 'error', 'output': f"Error running r2cmd: {e}\nCommand: {command}\nResponse: {res}" }
return f"Error running r2cmd: {e}\nCommand: {command}\nResponse: {res}"
def run_python(command: str):
"""
Run a python script and return the output
Parameters
----------
command: str
The python script to run
Returns
-------
str
The output of the python script
"""
r2 = get_r2_inst()
res = ""
is_plugin = False
python_path = sys.executable
try:
proc = subprocess.run([python_path, '-c', command],
capture_output=True,
text=True)
res = proc.stdout
if proc.stderr:
res += proc.stderr
except Exception as e:
res = str(e)
# if is_plugin:
# base64cmd = base64.b64encode(command.encode('utf-8')).decode('utf-8')
# res += r2cmd(f'#!python -e base64:{base64cmd} > .r2ai_tmp.log')
# res += r2cmd('cat .r2ai_tmp.log')
# r2cmd('rm .r2ai_tmp.log')
# else:
# with open('r2ai_tmp.py', 'w') as f:
# f.write(command)
# r2 = get_r2_inst()
# res += r2cmd('#!python r2ai_tmp.py > .r2ai_tmp.log')
# time.sleep(0.1)
# res += r2cmd('!cat .r2ai_tmp.log')
# LOGGER.debug(f'run_python: {res}')
# # r2cmd('rm r2ai_tmp.py')
# # r2cmd('rm .r2ai_tmp.log')
return res
schemas = {
"execute_binary": {
"name": "execute_binary",
"description": "Execute a binary with the given arguments and stdin",
"parameters": {
"type": "object",
"properties": {
"args": {
"description": "The arguments to pass to the binary. Do not include the file name.",
"type": "array",
"items": {
"type": "string"
}
},
"stdin": {
"type": "string"
}
}
}
}
}
def execute_binary(args: list[str] = [], stdin: str = ""):
filename = get_filename()
if filename:
if os.path.isabs(filename):
abs_path = os.path.abspath(filename)
if os.path.exists(abs_path):
filename = abs_path
else:
cwd_path = os.path.join(os.getcwd(), filename)
if os.path.exists(cwd_path):
filename = cwd_path
try:
cmd = [filename] + args
proc = subprocess.run(cmd, input=stdin, capture_output=True, text=True)
res = proc.stdout
if proc.stderr:
res += proc.stderr
return res
except Exception as e:
return str(e)
return ""
# r2 = get_r2_inst()
# if stdin:
# r2.cmd(f'dor stdin={json.dumps(stdin)}')
# if len(args) > 0:
# r2.cmd(f"ood {' '.join(args)}")
# else:
# r2.cmd("ood")
# res = r2cmd("dc")
# return res
def print_tool_call(msg):
if msg['function']['name'] == 'r2cmd':
builtins.print('\x1b[1;32m> \x1b[4m' + msg['function']['arguments']['command'] + '\x1b[0m')
elif msg['function']['name'] == 'run_python':
builtins.print('\x1b[1;32m> \x1b[4m' + "#!python" + '\x1b[0m')
builtins.print(msg['function']['arguments']['command'])
elif msg['function']['name'] == 'execute_binary':
filename = get_filename() or 'bin'
stdin = msg['function']['arguments']['stdin'] if 'stdin' in msg['function']['arguments'] else None
args = msg['function']['arguments']['args'] if 'args' in msg['function']['arguments'] else []
cmd = filename
if args and len(args) > 0:
cmd += ' ' + ' '.join(args)
if stdin and len(stdin) > 0:
cmd += f' stdin={stdin}'
builtins.print('\x1b[1;32m> \x1b[4m' + cmd + '\x1b[0m')
File renamed without changes.
+350
View File
@@ -0,0 +1,350 @@
from textual.app import App, ComposeResult, SystemCommand
from textual.containers import ScrollableContainer, Container, Horizontal, VerticalScroll, Grid, Vertical # Add Vertical to imports
from textual.widgets import Header, Footer, Input, Button, Static, DirectoryTree, Label, Tree, Markdown
from textual.command import CommandPalette, Command, Provider, Hits, Hit
from textual.screen import Screen, ModalScreen, SystemModalScreen
from textual.message import Message
from textual.reactive import reactive
from .model_select import ModelSelect
from r2ai.pipe import open_r2, get_filename, r2
from r2ai.interpreter import is_litellm_model
from typing import Iterable
import os
from pathlib import Path
from textual import work
from textual.widget import Widget
from textual.css.query import NoMatches
from textual import log
from litellm import validate_environment
from markdown_it import MarkdownIt
# from ..repl import set_model, r2ai_singleton
# ai = r2ai_singleton()
from .chat import chat
import asyncio
import json
import re
ANSI_REGEX = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
class ModelConfigDialog(SystemModalScreen):
def __init__(self, keys: list[str]) -> None:
super().__init__()
self.keys = keys
def compose(self) -> ComposeResult:
for key in self.keys:
yield Input(placeholder=key, id=f"{key}-input")
yield Button("Save", variant="primary", id="save-button")
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "save-button":
for key in self.keys:
os.environ[key] = self.query_one(f"#{key}-input", Input).value
self.dismiss()
class ChatMessage(Widget):
markdown: reactive[str] = reactive("")
sender = "User"
is_done = reactive(False, layout=True, recompose=True)
def __init__(self, id: str, sender: str, content: str, is_done: bool = False, **kwargs) -> None:
super().__init__(id=id, classes='chat-message-container', **kwargs)
# self.markdown = f"*{sender}*: {content}"
self.markdown = content
self.sender = sender
self.is_done = is_done
async def watch_markdown(self, markdown: str) -> None:
try:
mkd = self.query_exactly_one(".text1")
text = self.markdown
if hasattr(mkd, 'update') and callable(mkd.update):
update_method = mkd.update
if asyncio.iscoroutinefunction(update_method):
await update_method(text)
else:
update_method(text)
except NoMatches:
pass
def add_text(self, markdown: str) -> None:
self.markdown += markdown
def compose(self) -> ComposeResult:
if self.sender == "User":
yield Static(f"[bold green]{self.sender}", classes='label_sender', markup=True)
yield Static(self.markdown, classes='text1', markup=True)
elif self.sender == 'AI':
yield Static(f"[bold magenta]{self.sender}", markup=True)
if self.is_done:
yield Markdown(self.markdown, classes='text1', parser_factory=lambda: MarkdownIt('gfm-like'))
else:
yield Static(self.markdown, classes='text1', markup=True)
elif self.sender == 'Tool Call':
yield Static(f"[bold blue]{self.sender}", markup=True)
yield Static(self.markdown, classes='text1', markup=True)
elif self.sender == 'Tool Response':
yield Static(f"[bold blue]{self.sender}", markup=True)
yield VerticalScroll(Static(self.markdown, classes='text1', markup=True), classes='tool_response_container')
class R2AIApp(App):
CSS_PATH = "app.tcss"
BINDINGS = [
("ctrl+p", "show_command_palette", "Command Palette"),
("ctrl+m", "select_model", "Select Model"),
("ctrl+o", "load_binary", "Load Binary")
]
TITLE = "r2ai"
SUB_TITLE = None
def __init__(self, *args, **kwargs) -> None:
super().__init__()
if 'ai' in kwargs:
self.ai = kwargs['ai']
else:
class FakeAI:
model = 'gpt-4o'
messages = []
self.ai = FakeAI()
self.update_sub_title(get_filename())
def update_sub_title(self, binary: str = None) -> str:
sub_title = None
model = self.ai.model
if binary and model:
binary = Path(binary).name
sub_title = f"{model} | {binary}"
elif binary:
sub_title = binary
else:
sub_title = model
self.sub_title = sub_title
def compose(self) -> ComposeResult:
yield Header()
yield Container(
VerticalScroll(
id="chat-container",
),
ScrollableContainer(
Horizontal(
Input(placeholder="Type your message here...", id="chat-input"),
Button("Send", variant="primary", id="send-button"),
id="input-container",
),
id="input-area",
),
id="content",
)
yield Footer()
def on_mount(self) -> None:
self.install_screen(CommandPalette(), name="command_palette")
self.query_one("#chat-input", Input).focus()
# self.install_screen(BinarySelectDialog(), name="binary_select_dialog")
def action_show_command_palette(self) -> None:
self.push_screen("command_palette")
async def select_model(self) -> None:
model = await self.push_screen_wait(ModelSelect())
if model:
await self.validate_model()
self.ai.model = model
self.notify(f"Selected model: {self.ai.model}")
self.update_sub_title()
@work
async def action_select_model(self) -> None:
await self.select_model()
@work
async def action_load_binary(self) -> None:
binary = await self.push_screen_wait(BinarySelectDialog())
if binary:
self.notify(f"Selected binary: {binary}")
self.update_sub_title(binary)
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
yield from super().get_system_commands(screen)
yield SystemCommand("Models", "Select Model", self.action_select_model)
yield SystemCommand("Load Binary", "Load Binary", self.action_load_binary) # Add this command
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "send-button":
self.send_message()
async def on_input_submitted(self, event: Input.Submitted) -> None:
if event.input.id == "chat-input":
await self.send_message()
def on_message(self, type: str, message: any) -> None:
if type == 'message':
existing = None
try:
existing = self.query_one(f"#{message['id']}")
if message["content"] is not None:
existing.add_text(message["content"])
if message['done']:
existing.is_done = True
self.scroll_to_bottom()
except NoMatches:
existing = self.add_message(message["id"], "AI", message["content"])
elif type == 'tool_call':
if 'command' in message['function']['arguments']:
self.add_message(message["id"], "Tool Call", f"{message['function']['name']} > {message['function']['arguments']['command']}")
elif message['function']['name'] == 'execute_binary':
args = message['function']['arguments']
output = get_filename() or "bin"
if 'args' in args and len(args['args']) > 0:
output += f" {' '.join(args['args'])}\n"
if 'stdin' in args and len(args['stdin']) > 0:
output += f" stdin={args['stdin']}\n"
self.add_message(message["id"], "Tool Call", f"{message['function']['name']} > {output}")
elif type == 'tool_response':
self.add_message(message["id"], "Tool Response", ANSI_REGEX.sub('', message['content']))
async def send_message(self) -> None:
input_widget = self.query_one("#chat-input", Input)
message = input_widget.value.strip()
if message:
self.add_message(None, "User", message)
input_widget.value = ""
try:
await self.validate_model()
self.chat(message)
except Exception as e:
self.notify(str(e), severity="error")
@work(thread=True)
async def chat(self, message: str) -> None:
await chat(self.ai, message, lambda type, message: self.call_from_thread(self.on_message, type, message))
async def validate_model(self) -> None:
model = self.ai.model
if not model:
await self.select_model()
if is_litellm_model(model):
model = self.ai.model.replace(':', '/')
keys = validate_environment(model)
if keys['keys_in_environment'] is False:
await self.push_screen_wait(ModelConfigDialog(keys['missing_keys']))
return True
def add_message(self, id: str, sender: str, content: str) -> None:
chat_container = self.query_one("#chat-container", VerticalScroll)
try:
msg = ChatMessage(id, sender, content)
chat_container.mount(msg)
return msg
except Exception as e:
pass
finally:
self.scroll_to_bottom()
def scroll_to_bottom(self) -> None:
chat_scroll = self.query_one("#chat-container", VerticalScroll)
chat_scroll.scroll_end(animate=False)
class Message(Widget):
def __init__(self, message: str) -> None:
super().__init__()
self.content = f'[bold]{message.role}[/] {message.content}'
def render(self) -> str:
return Markdown(self.content)
class Messages(Container):
def __init__(self, messages) -> None:
self.messages = messages
def compose(self) -> ComposeResult:
for message in self.messages:
yield Message(message)
class FilteredDirectoryTree(DirectoryTree):
input_path: reactive[Path] = reactive(Path.home())
def filter_paths(self, paths: Iterable[Path]) -> Iterable[Path]:
ps = [path for path in paths if str(path).startswith(str(self.input_path))]
return ps
class BinarySelectDialog(SystemModalScreen):
BINDINGS = [
("up", "cursor_up", "Move cursor up"),
("down", "cursor_down", "Move cursor down"),
("enter", "select_cursor", "Select item"),
("escape", "app.pop_screen", "Close"),
("backspace", "go_up", "Go up one level"), # Add this binding
]
def compose(self) -> ComposeResult:
with Vertical():
yield Input(placeholder="Enter path here...", id="path-input")
yield FilteredDirectoryTree(Path.home(), id="file-browser")
def on_mount(self) -> None:
self.path_input = self.query_one("#path-input", Input)
self.file_browser = self.query_one("#file-browser", DirectoryTree)
self.path_input.value = str(get_filename() or Path.home())
self.path_input.focus()
self.watch(self.path_input, "value", self.update_tree)
@work(thread=True)
def update_tree(self) -> None:
path = Path(self.path_input.value)
if path.exists():
self.file_browser.path = str(path)
elif path.parent.exists():
self.file_browser.path = str(path.parent)
self.file_browser.input_path = str(path)
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "up-button":
self.go_up()
def action_go_up(self) -> None:
current_path = Path(self.file_browser.path)
parent_path = current_path.parent
if parent_path != current_path:
self.file_browser.path = str(parent_path)
self.path_input.value = str(parent_path)
def on_directory_tree_file_selected(self, event: DirectoryTree.FileSelected) -> None:
self.path_input.value = str(event.path)
self.open_and_analyze_binary(str(event.path))
self.dismiss(str(event.path))
@work(thread=True)
def open_and_analyze_binary(self, path: str) -> None:
global r2
r2 = open_r2(path)
res = json.loads(r2.cmd('{ "cmd":"aaa" }'))
if 'error' in res and res['error'] is True:
self.notify(res['error'], severity="error")
def on_directory_tree_directory_selected(self, event: DirectoryTree.DirectorySelected) -> None:
self.path_input.value = str(event.path)
def action_cursor_up(self) -> None:
self.file_browser.action_cursor_up()
def action_cursor_down(self) -> None:
self.file_browser.focus()
self.file_browser.action_cursor_down()
def action_select(self) -> None:
node = self.file_browser.cursor_node
if hasattr(node.data, 'is_file') and node.data.is_file:
self.open_and_analyze_binary(str(node.data.path))
self.dismiss(str(node.data.path))
# app = R2AIApp()
# app.run()
+129
View File
@@ -0,0 +1,129 @@
Placeholder {
height: 100%;
}
#sidemenu {
dock: left;
width: 15;
height: 100;
}
Screen {
background: $surface;
layers: base overlay;
}
#content {
height: 1fr;
margin: 1 2;
}
#chat-scroll {
height: 1fr;
border: solid $primary;
background: $surface-darken-1;
}
#chat-container {
padding: 1;
overflow-y: scroll;
layout: vertical;
layer: base;
}
#input-area {
layer: base;
height: auto;
margin-top: 1;
}
#input-container {
height: 3;
background: $boost;
border: solid $primary;
}
#chat-input {
dock: left;
width: 1fr;
background: $boost;
border: none;
}
#send-button {
width: 10;
margin-left: 1;
}
/* ... existing styles ... */
#binary-select-dialog Label {
padding: 1 2;
}
#binary-select-dialog #path-input {
margin: 0 2 1 2;
}
#binary-select-dialog #path-header {
height: 3;
margin-bottom: 1;
}
#binary-select-dialog #up-button {
width: 3;
min-width: 3;
margin-left: 1;
}
.chat-message-container {
height: auto;
width: 100%;
}
MarkdownFence {
height: auto;
overflow: auto;
max-height: 9999;
}
Static.text1 {
padding-left: 2;
padding-bottom: 1;
}
Static.label_sender {
width: auto;
}
.tool_response_container {
max-height: 50;
height: auto;
overflow: auto;
padding-bottom: 1;
border: solid white;
}
ModelSelect {
display: none;
align: center middle;
Container {
margin: 0;
width: 60%;
height: 50%;
overflow: hidden;
display: block;
OptionList {
margin-bottom: 3;
}
}
}
BinarySelectDialog {
display: none;
align: center middle;
Vertical {
background: transparent;
height: 50%;
width: 50%;
}
}
+25
View File
@@ -0,0 +1,25 @@
from litellm import acompletion, ChatCompletionAssistantToolCall, ChatCompletionToolCallFunctionChunk
import asyncio
import json
import signal
from r2ai.pipe import get_r2_inst
from r2ai.tools import run_python, r2cmd, execute_binary
from r2ai.repl import r2ai_singleton
from r2ai.auto import ChatAuto, SYSTEM_PROMPT_AUTO
from r2ai.interpreter import is_litellm_model
from r2ai.models import new_get_hf_llm
def signal_handler(signum, frame):
raise KeyboardInterrupt
async def chat(ai, message, cb):
model = ai.model.replace(":", "/")
tools = [r2cmd, run_python, execute_binary]
ai.messages.append({"role": "user", "content": message})
tool_choice = 'auto'
if not is_litellm_model(model) and ai and not ai.llama_instance:
ai.llama_instance = new_get_hf_llm(ai, model, int(ai.env["llm.window"]))
chat_auto = ChatAuto(model, interpreter=ai, system=SYSTEM_PROMPT_AUTO, tools=tools, messages=ai.messages, tool_choice=tool_choice, cb=cb)
return await chat_auto.achat()
+86
View File
@@ -0,0 +1,86 @@
from textual.app import ComposeResult
from textual.widgets import Input, OptionList
from textual.widget import Widget
from textual.widgets.option_list import Option
from textual.containers import Container
from textual.message import Message
from textual.binding import Binding
from textual.screen import ModalScreen, SystemModalScreen
from textual import log
from r2ai.models import models
# from ..repl import set_model, r2ai_singleton
# ai = r2ai_singleton()
# MODELS = models().split("\n")
from litellm import models_by_provider
MODELS = []
for model in models().split("\n"):
if model.startswith("-m "):
MODELS.append(model[3:])
for provider in models_by_provider:
for model in models_by_provider[provider]:
MODELS.append(f"{provider}/{model}")
class ModalInput(Input):
BINDINGS = [
Binding("down", "cursor_down", "Move down"),
]
class ModelSelect(SystemModalScreen):
BINDINGS = [
Binding("up", "cursor_up", "Move up"),
Binding("down", "cursor_down", "Move down"),
Binding("enter", "select", "Select model"),
Binding("escape", "app.pop_screen", "Close"),
]
class ModelSelected(Message):
"""Event emitted when a model is selected."""
def __init__(self, model: str) -> None:
self.model = model
super().__init__()
def compose(self) -> ComposeResult:
self.input = ModalInput(placeholder="Type to filter...")
self.option_list = OptionList()
with Container():
yield self.input
yield self.option_list
def on_mount(self) -> None:
self.options = []
for t in MODELS:
if t.startswith("-m "):
self.options.append(Option(t[3:], id=t[3:]))
elif len(t) > 0:
self.options.append(Option(t, id=t))
self.option_list.add_options(self.options)
self.filtered_options = self.options.copy()
self.input.focus()
def update_options(self, options):
self.option_list.clear_options()
self.option_list.add_options(options)
self.filtered_options = options
def on_input_changed(self, event: Input.Changed) -> None:
filter_text = event.value
filtered_options = [option for option in self.options if filter_text.lower() in option.id.lower()]
self.update_options(filtered_options)
def action_cursor_up(self) -> None:
self.option_list.action_cursor_up()
def action_cursor_down(self) -> None:
if self.option_list.has_focus:
self.option_list.action_cursor_down()
else:
self.option_list.focus()
def on_option_list_option_selected(self, index) -> None:
selected_index = index.option_index
if 0 <= selected_index < len(self.filtered_options):
selected_option = self.filtered_options[selected_index]
if not selected_option.disabled:
self.dismiss(selected_option.id)
+29 -4
View File
@@ -26,10 +26,8 @@ def merge_deltas(original, delta):
return original
def slurp(f):
fd = open(f, errors="ignore")
data = fd.read()
fd.close()
return str(data)
with open(f, errors="ignore") as fd:
return str(fd.read())
def dump(f, x):
fd = open(f, "w")
@@ -40,3 +38,30 @@ def syscmdstr(cmd):
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output, error = process.communicate()
return output.decode().strip()
def filter_print(*args, **kwargs):
_args = []
filter = None
if "filter" in kwargs:
filter = kwargs["filter"]
del kwargs["filter"]
for a in args:
new = ""
lines = str(a).splitlines()
if len(lines) > 1:
for line in lines:
if filter is not None:
if filter in line:
new += line + "\n"
else:
new += line + "\n"
else:
if filter is not None:
if filter in str(a):
new += str(a)
else:
new += str(a)
_args.append(new)
print(*_args, **kwargs)
+15 -4
View File
@@ -13,7 +13,7 @@ DEVICE = None
try:
import whisper
HAVE_WHISPER = True
except:
except Exception:
pass
have_festival = os.path.isfile("/usr/bin/festival")
@@ -50,8 +50,16 @@ def stt(seconds, lang):
return
tts("(r2ai) listening for 5s... ", "digues?", lang)
print(f"DEVICE IS {device}")
os.system("rm -f .audiomsg.wav")
rc = os.system(f"ffmpeg -f avfoundation -t 5 -i '{device}' .audiomsg.wav > /dev/null 2>&1")
try:
os.remove(".audiomsg.wav")
except OSError:
pass
result = subprocess.run(
["ffmpeg", "-f", "avfoundation", "-t", "5", "-i", device, ".audiomsg.wav"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
rc = result.returncode
if rc != 0:
tts("(r2ai)", "cannot record from microphone. missing permissions in terminal?", lang)
return
@@ -60,7 +68,10 @@ def stt(seconds, lang):
result = model.transcribe(".audiomsg.wav")
else:
result = model.transcribe(".audiomsg.wav", language=lang)
os.system("rm -f .audiomsg.wav")
try:
os.remove(".audiomsg.wav")
except OSError:
pass
tts("(r2ai)", "ok", lang)
text = result["text"].strip()
if text == "you":
+69 -10
View File
@@ -1,11 +1,18 @@
import _thread as thread
import platform
import json
import re
import time
from threading import Thread, Event
from .utils import get_timez
from . import LOGGER
ores = ""
kill_event = None
server = None
server_thread = None
server_loading = False
# OpenAI API endpoint here
def handle_v1_completions_default(self, ai, obj, runline2, method):
if "prompt" in obj:
@@ -294,6 +301,7 @@ def handle_custom_request(self, ai, msg, runline2, method):
return True
def start_http_server_now(ai, runline2):
global server_loading
import http.server
import socketserver
WANTCTX = ai.env["http.chatctx"] == "true"
@@ -325,15 +333,66 @@ def start_http_server_now(ai, runline2):
self.send_response(404)
self.end_headers()
self.wfile.write(bytes(f'Invalid request. Use {BASEPATH}'))
print("[R2AI] Serving at port", PORT)
LOGGER.getChild("server").info("[R2AI] Serving at port %s", PORT)
Handler.protocol_version = "HTTP/1.0"
server = socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler)
server.allow_reuse_address = True
server.allow_reuse_port = True
server.serve_forever()
global server
if server is not None:
server_loading = False
print("Already running")
return
server = None
try:
server = socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler)
server.allow_reuse_address = True
server.allow_reuse_port = True
server.serve_forever()
except Exception as e:
print(e)
server_loading = False
def start_http_server(ai, runline2, background):
if background:
thread.start_new_thread(start_http_server_now, (ai, runline2))
else:
start_http_server_now(ai, runline2)
global server_thread, server_loading
server_loading = True
server_thread = Thread(target=start_http_server_now, args=(ai, runline2))
server_thread.start()
while server_loading:
time.sleep(0.1)
global server
if server is None:
server_thread.join()
return
if not background:
global kill_event
kill_event = Event()
while not kill_event.is_set():
time.sleep(0.2)
kill_event = None
def stop_http_server(force=False):
if not force and server_in_background():
return
global server
if server:
LOGGER.getChild("server").info("Stopping server")
_kill_server()
server = None
global server_thread
if server_thread:
server_thread.join()
server_thread = None
if not server_in_background():
kill_event.set()
def server_running():
global server_thread
global server
return bool(server) and bool(server_thread)
def server_in_background():
global kill_event
return server_running and not bool(kill_event)
def _kill_server():
global server
server.shutdown()
Thread(target=server.server_close).start()
+21
View File
@@ -0,0 +1,21 @@
PWD=$(shell pwd)
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
R2_BINDIR=$(shell r2 -H R2_BINDIR)
install:
# rm -f $(R2PM_BINDIR)/r2ai-server
# ln -fs $(PWD)/r2ai-server/r2ai-server $(R2PM_BINDIR)/r2ai-server
mkdir -p $(R2_BINDIR)
cp -f r2ai-server $(R2_BINDIR)/r2ai-server || echo Maybe use user-install instead?
uninstall:
rm -f $(R2_BINDIR)/r2ai-server
user-install:
mkdir -p $(R2PM_BINDIR)
rm -f $(R2PM_BINDIR)/r2ai-server
ln -fs $(PWD)/r2ai-server $(R2PM_BINDIR)/r2ai-server
user-uninstall:
rm -f $(R2PM_BINDIR)/r2ai-server
+25
View File
@@ -0,0 +1,25 @@
# r2ai-server
Start ollama, llamacpp, llamafile, r2ai, kobaldcpp and other language model webservers using a the same syntax for all of them to simplify its launching and setup.
* Use -l to list the implementations available
You can install more via r2pm
* Use -m to list or select the model. Those models can be absolute paths or model names when downloaded via r2ai, which uses the hugging face api
## Running r2ai-server
- Get usage: `r2pm -r r2ai-server`
- List available servers: `r2pm -r r2ai-server -l`
- List available models: `r2pm -r r2ai-server -m`
On Linux, models are stored in `~/.r2ai.models/`. File `~/.r2ai.model` lists the default model and other models.
**Example launching a local Mistral AI server:**
```
$ r2pm -r r2ai-server -l r2ai -m mistral-7b-instruct-v0.2.Q2_K
[12/13/24 10:35:22] INFO r2ai.server - INFO - [R2AI] Serving at port 8080 web.py:336
```
+25 -11
View File
@@ -4,21 +4,31 @@ MODEL=""
PORT=8080
MODELDIR=~/.r2ai.models
LLAMA=llamafile
[ -z "${R2AI}" ] && R2AI=r2ai
main() {
if [ -z "${MODEL}" ]; then
echo "Use -l and -m to select a model"
echo "Use -l and -m to select a model" >&2
exit 1
fi
echo "${MODEL}" | grep -e ^/
if [ $? = 0 ]; then
MODELPATH="${MODEL}"
else
MODELPATH="${MODELDIR}/${MODEL}.gguf"
fi
case ${LLAMA} in
llamafile)
llamafile --server --nobrowser -c 0 --port ${PORT} -m "${MODELDIR}/${MODEL}.gguf"
llamafile --server --nobrowser -c 0 --port ${PORT} -m "${MODELPATH}"
;;
llamacpp)
llama-server -c 0 --port ${PORT} -m "${MODELDIR}/${MODEL}.gguf"
llama-server -c 0 --port "${PORT}" -m "${MODELPATH}"
;;
koboldcpp)
koboldcpp -c 0 --port ${PORT} -m "${MODELDIR}/${MODEL}.gguf"
koboldcpp -c 0 --port "${PORT}" -m "${MODELPATH}"
;;
r2ai)
${R2AI} --port "${PORT}" -m "${MODEL}" -w
;;
*)
echo "Invalid llama server selected."
@@ -29,11 +39,12 @@ main() {
help() {
cat <<EOF
Usage: r2ai-server [-flags]
-m list models
-m [name] select given model
-l list llama servers available
-h show this help message
-p [port] select port
-m list models
-m [name] select given model
-l list llama servers available
-l [name] select llama software
-h show this help message
-p [port] select port
EOF
}
@@ -44,6 +55,8 @@ models() {
}
llamas() {
# TODO check for those bins in path before suggesting them
echo r2ai
echo llamafile
echo llamacpp
echo koboldcpp
@@ -64,11 +77,11 @@ while : ; do
models
break
fi
MODEL=$2
MODEL="$2"
shift
;;
-p)
PORT=$2
PORT="$2"
shift
;;
-h)
@@ -76,6 +89,7 @@ while : ; do
;;
"")
main
break
;;
esac
shift
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 pancake
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+6 -91
View File
@@ -1,94 +1,9 @@
R2_USER_PLUGINS=$(shell r2 -H R2_USER_PLUGINS)
PWD=$(shell pwd)
R2PM_BINDIR=$(shell r2pm -H R2PM_BINDIR)
# Note that a bunch of packages are not available for 3.12 yet
ifeq ($(shell which python3.11 > /dev/null && echo ok),ok)
PYTHON?=python3.11
else
PYTHON?=python3
endif
PIP=$(PYTHON) -m pip
TARGETS=all clean mrproper user-install install user-uninstall uninstall fmt indent
LINTED=r2ai/code_block.py
LINTED+=r2ai/bubble.py
LINTED+=r2ai/const.py
LINTED+=main.py
LINTED+=r2ai/backend/kobaldcpp.py
# LINTED+=r2ai/index.py
# LINTED+=r2ai/voice.py
# LINTED+=r2ai/anthropic.py
$(TARGETS):
$(MAKE) -C src $@
ifeq ($(R2PM_BINDIR),)
FATAL ERROR
endif
test: all
$(MAKE) -C test
.PHONY: all all.old deps clean deps-global pub lint cilint
.PHONY: install uninstall user-install user-uninstall
all: venv
@./r2ai.sh
large:
. venv/bin/activate ; $(PYTHON) main.py -l
all.old:
@test -n "${VIRTUAL_ENV}" || (echo "Run:"; echo ". venv/bin/activate" ; exit 1)
$(PYTHON) main.py || $(MAKE) deps
venv:
$(PYTHON) -m venv venv
if [ -z "`find venv | grep llama_cpp`" ]; then . venv/bin/activate ; pip install -r requirements.txt ; fi
deps: venv
#test -n "${VIRTUAL_ENV}" || (echo "Run: . venv/bin/activate" ; exit 1)
. venv/bin/activate && export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
pip install --force-reinstall -U -r requirements.txt --no-cache-dir
$(MAKE) vectordb
clean:
rm -rf venv vectordb vdb
mrproper:
$(MAKE) clean
deps-global:
export CMAKE_ARGS="-DLLAMA_METAL=on -DLLAMA_METAL_EMBED_LIBRARY=ON" && \
$(PIP) install --force-reinstall -U -r requirements.txt --break-system-packages --no-cache-dir
vdb vectordb:
git clone https://github.com/kagisearch/vectordb vdb
cat vdb/setup.py | grep -v tensorflow_text > .x && mv .x vdb/setup.py
. venv/bin/activate \
&& cd vdb \
&& $(PIP) install setuptools tensorflow_hub \
&& $(PYTHON) setup.py build \
&& $(PYTHON) setup.py install
user-install:
ln -fs $(PWD)/r2ai-server $(R2PM_BINDIR)/r2ai-server
ln -fs $(PWD)/r2ai.sh $(R2PM_BINDIR)/r2ai
install: user-install
-mkdir -p /usr/local/share/man/man1/r2ai.1
-cp doc/usage/r2ai.1 /usr/local/share/man/man1/r2ai.1
install-plugin user-install-plugin:
ln -fs $(PWD)/main.py $(R2_USER_PLUGINS)/r2ai.py
uninstall user-uninstall:
rm -f $(R2PM_BINDIR)/r2ai
-rm -f /usr/local/share/man/man1/r2ai.1
user-uninstall-plugin uninstall-plugin:
rm -f $(R2_USER_PLUGINS)/r2ai.py
pub:
$(PYTHON) setup.py build sdist
twine check dist/*
twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/*
cilint:
pylint $(LINTED)
lint:
pylint *.py r2ai/*.py
.PHONY: $(TARGETS) test
+84 -79
View File
@@ -1,110 +1,115 @@
```
,______ .______ .______ ,___
: __ \ \____ |: \ : __|
| \____|/ ____|| _,_ || : |
| : \ \ . || : || |
| |___\ \__:__||___| || |
|___| : |___||___|
* --pancake
# R2AI - Augmented reversing with LLM for radare2
```console
╭─────────────────────────────────╮
│ ,______ .______ .______ ,___ │
╭──╮ │ : __ \ \____ |: \ : __| │
│ _│_ │ | \____|/ ____|| _,_ || : | │
│ O O < | : \ \ . || : || | │
│ │╷ │ | |___\ \__:__||___| || | │
│ ││ │ |___| : |___||___| │
│ ─╯│ ╰─────────────────────────────────╯
╰───╯
```
[![ci](https://github.com/radareorg/r2ai/actions/workflows/ci.yml/badge.svg)](https://github.com/radareorg/r2ai/actions/workflows/ci.yml)
[![radare2](https://img.shields.io/badge/radare2-6.0.4-green)](https://github.com/radareorg/radare2)
Run a language model in local, without internet, to entertain you or help answering questions about radare2 or reverse engineering in general. Note that models used by r2ai are pulled from external sources which may behave different or respond unrealible information. That's why there's an ongoing effort into improving the post-finetuning using memgpt-like techniques which can't get better without your help!
## Components
<p align="center">
<img src="doc/r2clippy.jpg">
</p>
This repository contains two plugins for radare2:
* **r2ai** - native [AI plugin](./src/README.md) for radare2
* **decai** - r2js plugin with special [focus on decompilation](./decai/README.md)
If you are looking to use radare2 with other agents via MCP:
* **r2mcp** - the [official radare2 mcp](https://github.com/radare2/radare2-mcp)
* **r2copilot** - the mcp with focus on CTF [r2copilot](https://github.com/darallium/r2-copilot)
If you are looking for a radare2 focused autonomous agent:
* **r2agent** - automate radare2 workflows through autonomous agents. [r2agent](https://github.com/nitanmarcel/r2agent)
## Features
* Prompt the language model without internet requirements
* Use local GGUF or remote language models (via http)
* Index large codebases or markdown books using a vector database
* Slurp file and perform actions on that
* Embed the output of an r2 command and resolve questions on the given data
* Define different system-level assistant role
* Set environment variables to provide context to the language model
* Configure different roles and customize prompts
* Scriptable via r2pipe via the r2ai command
* Live with repl and batch mode from cli or r2 prompt
* Accessible as an r2lang-python plugin, keeps session state inside radare2
* Scriptable from python, bash, r2pipe, and javascript (r2papi)
* Use different models, dynamically adjust query template
* Load multiple models and make them talk between them
* Support Automatic (ReAct) mode to solve tasks using function calling
* Use local and remote language models (ollama, openai, grok, anthropic, ..)
* RAG markdown, code or textfiles using its native vector database
* Embed the output of an r2 command and resolve questions on the given data
## User defined Prompts
```console
[0x00000000]> r2ai -q
explain: Explain the current function -
devices: Find and explain devices used -
libs: Group imports by Libraries -
varnames: Better variable names -
autoname: Automatically suggest a better name for this function -
vulns: Find vulnerabilities or bugs in the current function -
signature: Suggest an improved function signature -
dlopen: List libraries loaded with dlopen - Some libraries are loaded
decompile: Augmented decompilation based on LLM -
[0x00000000]>
```
## Installation
To use `r2ai`, you need to setup a venv and install all the python dependencies inside. Just running `make` or `r2pm -ci r2ai` should be enough.
The recommended way to install any of the r2ai components is via r2pm:
You can optionally install vectordb to index local data by typing: `make vdb`. If the process fails `rm -rf vdb` and try again
## Running
When running installed via r2pm you can execute it like this:
```bash
r2pm -r r2ai
```console
$ r2pm -Uci r2ai
$ r2pm -Uci decai
```
Additionally you can get the `r2ai` command inside r2 to run as an rlang plugin by installing the bindings:
## Using r2ai
```bash
r2pm -i rlang-python
make user-install
- Adds the **r2ai** command to the radare2 shell: `r2 -qc r2ai`
- You can also run the wrapper in $PATH: `r2pm -r r2ai`
Drop your API keys in environment variables or use the configuration file:
```console
$ export ANTHROPIC_API_KEY=sk-ant-api03-CENSORED
$ export OPENAI_API_KEY=sk-proj-6rlSPS-zN1v...
```
After this you should get the `r2ai` command inside the radare2 shell. Set the `R2_DEBUG=1` environment to see the reasons why the plugin is not loaded if it's not there.
Or edit the api keys file `~/.config/r2ai/apikeys.txt` run:
## Windows
On Windows you may follow the same instructions, just ensure you have the right python environment ready and create the venv to use
```cmd
git clone https://github.com/radareorg/r2ai
cd r2ai
set PATH=C:\Users\YOURUSERNAME\Local\Programs\Python\Python39\;%PATH%
python3 -m pip -r requirements.txt
python3 main.py
```console
$ r2ai -K
```
## Usage
## Saving settings
There are 4 different ways to run `r2ai`:
You may customize and save your configuration settings using your OS's default settings file (e.g `~/.radare2rc` on Linux).
For example, the following configuration sets Claude 3.7 by default, with max output tokens to 64000.
* Standalone and interactive: `r2pm -r r2ai` or `python main.py`
* Batch mode: `r2ai '-r act as a calculator' '3+3=?'`
* As an r2 plugin: `r2 -i main.py /bin/ls`
* From radare2 (requires `r2pm -ci rlang-python`): `r2 -c 'r2ai -h'`
* Using r2pipe: `#!pipe python main.py`
* Define a macro command: `'$r2ai=#!pipe python main.py`
## Auto mode
When using OpenAI, Claude or any of the Functionary local models you can use the auto mode which permits the language model to execute r2 commands, analyze the output in loop and in a loop until it is resolved. Here's a sample session to achieve that:
* Video https://infosec.exchange/@radareorg/111946255058894583
```bash
$ . env/bin/activate
(env)$ r2 /bin/ls
[0x00000000]> '$r2ai=#!pipe python main.py
[0x00000000]> $r2ai '-m openai:gpt-4'
[0x00000000]> $r2ai "' list the imports for this program"
[0x00000000]> $r2ai "' draw me a donut"
[0x00000000]> $r2ai "' decompile current function and explain it"
```console
$ r2ai -E
```
## Examples
then you can type the commands you want to run when the r2ai plugin is loaded:
You can interact with r2ai from standalone python, from r2pipe via r2 keeping a global state or using the javascript interpreter embedded inside `radare2`.
```console
r2ai -e api=anthropic
r2ai -e model=claude-sonnet-5-5
r2ai -e max_tokens=64000
```
* [conversation.r2.js](examples/conversation.r2.js) - load two models and make them talk to each other
## Further Reading
### Development/Testing
* There's [a chapter](https://book.rada.re/plugins/r2ai.html) in the official r2book
* Cryptax on [lmstudio+gptoss](https://cryptax.medium.com/r2ai-with-lmstudio-and-gpt-oss-08efa5ea2476) blog post
* Malware analysis [with r2ai](https://arxiv.org/pdf/2504.07574) by Cryptax and Daniel Nakov
* Analysis of [Linux/Trigona ransomware](https://cryptax.medium.com/linux-trigona-analysis-with-r2ai-3e2bd1815e52), [Linux/Prometei botnet](https://cryptax.medium.com/reversing-a-prometei-botnet-binary-with-r2-and-ai-part-one-3cdb3dc6ffab) and [W32/SkyAI](https://cryptax.medium.com/w32-skyai-uses-ai-so-do-i-d33f04d63534with) with r2ai
Just run `make` .. or well `python3 main.py`
## Videos
### TODO
* add "undo" command to drop the last message
* dump / restore conversational states (see -L command)
* Implement `~`, `|` and `>` and other r2shell features
- [Solving a crackme](https://infosec.exchange/@radareorg/111946255058894583)
- [De-obfuscation of malware Linux/Ladvix](https://asciinema.org/a/724126)
- [Analysis of the /fast option inside Linux/Trigona ransomware](https://asciinema.org/a/pBPEaJhp6cunWSKFpBUDTgPt4)
+1
View File
@@ -0,0 +1 @@
1.5.4
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
vim decai/src/constants.ts
make -C decai/src
vim src/r2ai.h
vim src/meson.build
vim VERSION
+13
View File
@@ -0,0 +1,13 @@
# Decompiler based AI
## Actions
* Build: `make -C src`
* Install: `make user-install`
* Test: `r2 -A -qc "decai -e api=ollamacloud;decai -e model=glm-5;decai -d @ 0x100000924" /bin/ls`
## Hints
* Never read the minified, autogenerated `decai.r2.js`
* Focus and work only on the TypeScript source
* Never run `tsc` or `npx`. Always use `make` targets to compile
+29
View File
@@ -0,0 +1,29 @@
PD=$(shell r2 -H R2_USER_PLUGINS)
SPD=$(shell r2 -H R2_LIBR_PLUGINS)
BF=/bin/dd
all:
@echo "Run: 'make user-install' to install it in your home"
@echo "Run: 'make -C src' to recompile the r2.js"
test:
r2 -i decai.r2.js -c 'decai -e deterministic=true' -caf $(BF)
fmt indent:
$(MAKE) -C src fmt
user-install:
mkdir -p "$(PD)"
cp decai.r2.js $(PD)/decai.r2.js
user-uninstall:
rm -f $(PD)/decai.r2.js
install:
make user-install PD=$(SPD)
uninstall:
make user-uninstall PD=$(SPD)
vs open:
open -a "Visual Studio Code" src
+238
View File
@@ -0,0 +1,238 @@
# Decai
The AI based Decompiler plugin for radare2
- Written in plain Javascript
- No dependencies than radare2 and curl in PATH
- Uses local ollama by default, but also r2ai-server
- Use services like Anthropic, OpenAI, HF, XAI, DeepSeek, ..
Features
- Auto mode with function calling with ANY model
- Uses the r2 pseudo decompiler by default, supports any other
- Explain purpose and auto-document functions
- Recursive decompilation to inline stubs
- Autoname functions and perform type propagation
- Find vulnerabilities, guide you and write exploits
- Choose any natural language (not just English)
- Choose output programming language (not just C)
- Chain queries to manually fine tune the results
- Customize decompilation prompt at any time
## Installation
Using r2pm: `r2pm -ci decai`
From source: `make user-install`
## Setup
```console
[0x00000000]> decai
Usage: decai (-h) ...
decai -H - help setting up r2ai
decai -d [f1 ..] - decompile given functions
decai -dr - decompile function and its called ones (recursive)
decai -dd [..] - same as above, but ignoring cache
decai -dD [query]- decompile current function with given extra query
decai -e - display and change eval config vars
decai -E - edit decai.txt
decai -h - show this help
decai -i [f] [q] - include given file and query
decai -n - suggest better function name
decai -q [text] - query language model with given text
decai -Q [text] - query on top of the last output
decai -r - change role prompt (same as: decai -e prompt)
decai -R - reset role prompt to default prompt
decai -s - function signature
decai -v - show local variables
decai -V - find vulnerabilities
decai -x - eXplain current function
```
Configuration options:
```console
[0x00000000]> decai -e
decai -e api=ollama
decai -e apitype=chat
decai -e host=http://localhost
decai -e port=11434
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
decai -e ctxfile=
decai -e headers=
decai -e cmds=pdc
decai -e cache=false
decai -e lang=C
decai -e hlang=English
decai -e debug=false
decai -e model=
decai -e maxinputtokens=-1
```
## Running decai
Decai is used from `r2` (e.g `r2 ./mybinary`). Get help with `decai -h`:
```
[0x00406cac]> decai -h
Usage: decai (-h) ...
decai -H - help setting up r2ai
decai -a [query] - solve query with auto mode
decai -d [f1 ..] - decompile given functions
decai -dr - decompile function and its called ones (recursive)
decai -dd [..] - same as above, but ignoring cache
decai -dD [query]- decompile current function with given extra query
...
```
List configuration variables with `decai -e`:
```
[0x00406cac]> decai -e
decai -e api=ollama
decai -e host=http://localhost
decai -e port=11434
decai -e prompt=Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change 'goto' into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and strings from comments like 'string:'
decai -e ctxfile=
...
```
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=?`:
```
[0x00406cac]> decai -e api=?
r2ai
claude
openapi
...
```
Extra headers can be injected globally with `decai -e headers=...` or from the
environment with `DECAI_HEADERS` / `R2AI_HEADERS`. Use one header per line, for
example:
```console
[0x00406cac]> decai -e headers=Authorization: Bearer sk-123\nUser-Agent: curl/8.7.1
```
### Example using a local model and ollama
For example, if Ollama serves model codegeex4:latest (`ollama ls`), set decai
API as `ollama` and model `codegeex4:latest`.
```
[0x00002d30]> decai -e api=ollama
[0x00002d30]> decai -e model=codegeex4:latest
[0x00002d30]> decai -q Explain what forkpty does in 2 lines
The `forkpty` function creates a new process with a pseudo-terminal, allowing the parent to interact with the child via standard input/output/err and controlling its terminal.
```
## Examples
See
[https://github.com/radareorg/r2ai-examples](https://github.com/radareorg/r2ai-examples)
```c
$ cat stack-overflow/bug.c
#include <string.h>
int main(int argc, char **argv) {
char buf[32];
strcpy (buf, argv[1]);
return 0;
}
```
```c
$ r2 buffer-overflow/a.out
[0x100003f58]> decai -d
int main(int argc, char **argv, char **envp) {
char buffer[32];
int result = 0;
if (argc > 1 && argv[1] != NULL) {
strcpy(buffer, argv[1]);
}
return result;
}
```
### Example using a local Mistral model and r2ai-server
For example, assuming we have a _local_ Mistral AI server running on port 8080
with `r2ai-server`, we can decompile a given function with `decai -d`. The
server shows it received the question:
```
GET
CUSTOM
RUNLINE: -R
127.0.0.1 - - [13/Dec/2024 10:40:49] "GET /cmd/-R HTTP/1.1" 200 -
GET
CUSTOM
RUNLINE: -i /tmp/.pdc.txt Rewrite this function and respond ONLY with code, NO explanations, NO markdown, Change goto into if/else/for/while, Simplify as much as possible, use better variable names, take function arguments and and strings from comments like string:. Transform this pseudocode into C
```
### Example using a Mistral API key
Put the API key in `~/.r2ai.mistral-key`.
````
[0x000010d0]> decai -e api=mistral
[0x000010d0]> decai -d main
```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv, char **envp) {
char password[40];
char input[40];
...
````
### Example with ChatGPT 4
```
[0x00406cac]> decai -e api=openai
[0x00406cac]> decai -d
#include <stdio.h>
#include <unistd.h>
void daemonize() {
daemon(1, 0);
}
...
```
## 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.
+171
View File
@@ -0,0 +1,171 @@
📦
27680 /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
Decai is the radare2 plugin for decompiling functions with the help of language models.
By default uses a local ollama server, but can you can pick any other service by using 'decai -e api=?'.
[0x00000000]> decai -e api=?
r2ai deepseek anthropic claude gemini groq hf lmstudio mistral ollama ollamacloud openapi openai openrouter vllm xai zen
## Using Ollama
* Visit https://ollama.com to install it.
* Download the model of choice: 'ollama run llama3.3'
* Configure decai to use the given model with: 'decai -e model=?'
These are the most recommended models for decompiling in local:
* hhao/qwen2.5-coder-tools:latest (18GB of ram)
* hhao/qwen2.5-coder-tools:32b (24GB of ram required)
## Common Options
* 'decai -e baseurl=<url>' override default host and port for API endpoint (e.g., 'http://localhost:11434')
* '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
* 'decai -e lang=Python' to output the decompilation in Python instead of C
* 'decai -e hlang=Catalan' to add comments or explanations in that language (instead of English)
* 'decai -e cmds=pdd,pdg' use r2dec and r2ghidra instead of r2's pdc as input for decompiling
* 'decai -e prompt=..' default prompt must be fine for most models and binaries, feel free to tweak it
## API Keys
Remove services like OpenAI, Mistral, Anthropic, Grok, Gemini, .. require API keys to work.
See 'decai -k' to list the status of available APIkeys
Use 'decai -K' to edit apikeys.txt and 'decai -E' to edit decai.txt.
Decai will pick them from the environment or the config files in your home:
* echo KEY > ~/.r2ai.openai-key
* export OPENAI_API_KEY=...
* put one 'key=value' pair per line in ~/.config/r2ai/decai.txt
## Using the R2AI Server:
Install r2ai or r2ai-server with r2pm:
[0x0000000]> decai -e api=r2ai
[0x0000000]> r2pm -ci r2ai
Choose one of the recommended models (after r2pm -r r2ai):
* -m ibm-granite/granite-20b-code-instruct-8k-GGUF
* -m QuantFactory/granite-8b-code-instruct-4k-GGUF
* -m TheBloke/Mistral-7B-Instruct-v0.2-GGUF
Start the webserver:
$ r2pm -r r2ai-server -l r2ai -m granite-8...
`,auto:`# Radare2 Auto Mode
Use function calling to execute radare2 commands in order to resolve the user request defined in the "User Prompt" section, analyze the responses attached in the "Command Results" section.
## Function Calling
Respond ONLY using plain JSON. Process user query and decide which function calls are necessary to solve the task.
1. Analyze the user request to determine if we need to run commands to extend the knowledge and context of the problem.
2. If function call is needed, construct the JSON like this:
- Fill the "action" key with the "r2cmd" value.
- Specify the "command" as a string.
- Optionally, provide a "reason" and "description"
3. If the answer can be provided and no more function calls are required:
- Use the key "action": "reply".
- Include "response" with the direct answer to the user query.
Return the result as a JSON object.
### Sample Function Calling Communication
Command Results: already performed actions with their responses
User Prompt: "Count how many functions we have here."
Response:
{
"action": "r2cmd",
"command": "aflc",
"description": "Count functions"
"reason": "Evaluate if the program is analyzed before running aaa"
}
## Rules
Use radare2 to resolve user requests.
* Explain each step in the "reason" field of the JSON.
* Follow the initial analysis instructions.
* Output only valid JSON as specified.
* Decompile and inspect functions, starting from main.
* Run only the needed commands to gather info.
* Use "@ (address|symbol)" to seek temporarily.
* Output should be a verbose markdown report.
* Use "sym." or "fcn." prefixes if "pdc" is empty.
* If a seek fails, use "f~name" to find the symbol's address.
### Initial Analysis
1. Run "aflc" to count the number of functions
2. If the output of "aflc" is "0" run "aaa" once, then "aflc" again
3. Run only one command at a time (do not use ";")
### 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(`
`).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:
[START]
`+r2.cmd("cat "+r.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+`:
[START]
`+I+`
[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,`
INPUT
`,g,`
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
`;for(let a of n){let s=JSON.parse(a);o+="### "+s.command+"\n\n```\n"+s.response+"\n```\n"}}o+=`
## User Prompt
`+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(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==="?"){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();
+13
View File
@@ -0,0 +1,13 @@
PACKAGE=decai
DEPENDS=radare2
SECTION=user/shell
PRIORITY=optional
MAINTAINER=pancake <[email protected]>
VERSION=$(shell git tag | tail -n 1)
# arch
UNAMEM=$(shell uname -m)
ifeq ($(UNAMEM),x86_64)
ARCH=amd64
else
ARCH=arm64
endif
+2
View File
@@ -0,0 +1,2 @@
AI based decompiler plugin for radare2
r2js plugin for radare2 that provides several features for reverse engineers, from autonaming functions, improve decompiling outputs, find bugs or explain functions to automated crackme solving using local and remote models if API keys are provided..
+34
View File
@@ -0,0 +1,34 @@
include ./CONFIG
UNAME=$(shell uname)
SUDO?=sudo
DEPENDS=
CROSSARCH=x64
R2CFG_FLAGS?=
PWD=$(shell pwd)
PACKAGE_DIR?=${PWD}
R2_VERSION=$(shell r2 -qv)
DOCKCROSS=$(PWD)/../dockcross
R2PLUGDIR=/usr/lib/radare2/$(R2_VERSION)
all: root
$(SUDO) rm -rf control data
$(MAKE) clean
mkdir -p data
cp -rf root/* data
$(MAKE) control
$(MAKE) deb
root:
mkdir -p root/$(R2PLUGDIR)
cp -f ../../decai.r2.js root/$(R2PLUGDIR)
purge: clean
rm -rf root
summary:
echo $(VERSION)
include deb.mk
+130
View File
@@ -0,0 +1,130 @@
# Create .deb without using dpkg tools.
#
# Author: Tim Wegener <[email protected]>
#
# Use 'include deb_hand.mak' after defining the user variables in a local
# makefile.
#
# The 'data' rule must be customised in the local make file.
# This rule should make a 'data' directory containing the full file
# layout of the installed package.
#
# This makefile will create a debian-binary file a control directory and a
# a build directory in the current directory.
# Do 'make clobber' to remove these generated files.
#
# Destination:
# PACKAGE_DIR - directory where package (and support files) will be built
# defaults to the current directory
#
# Sources:
# SOURCE_DIR - directory containing files to be packaged
# ICON_SOURCE - 26x26 icon file for maemo
# DESCR - description with summary on first line
# preinst, postinst, prerm, postrm - optional control shell scripts
# These fields are used to build the control file:
# PACKAGE =
# VERSION =
# ARCH =
# SECTION =
# PRIORITY =
# MAINTAINER =
# DEPENDS =
#
# SOURCE_DIR =
# ICON_SOURCE =
# (ICON_SOURCE is optional)
# *** NO USER CHANGES REQUIRED BEYOND THIS POINT ***
ifeq ($(shell uname),Darwin)
MD5SUM=md5
else
MD5SUM=md5sum
endif
GAWK=awk
PACKAGE_DIR=$(shell pwd)
CONTROL_EXTRAS ?= ${wildcard preinst postinst prerm postrm}
${PACKAGE_DIR}/control: ${PACKAGE_DIR}/data ${CONTROL_EXTRAS} DESCR \
${ICON_SOURCE}
#rm -rf $@
mkdir -p $@
ifneq (${CONTROL_EXTRAS},)
cp ${CONTROL_EXTRAS} $@
endif
# Make control file.
echo "Package: ${PACKAGE}" > $@/control
echo "Version: ${VERSION}" >> $@/control
echo "Section: ${SECTION}" >> $@/control
echo "Priority: ${PRIORITY}" >> $@/control
echo "Architecture: ${ARCH}" >> $@/control
ifneq (${DEPENDS},)
echo "Depends: ${DEPENDS}" >> $@/control
endif
echo "Installed-Size: ${shell du -s ${PACKAGE_DIR}/data|cut -f1}" \
>> $@/control
echo "Maintainer: ${MAINTAINER}" >> $@/control
printf "Description:" >> $@/control
cat DESCR | ${GAWK} '{print " "$$0;}' >> $@/control
#ifneq (${ICON_SOURCE},)
# echo "Maemo-Icon-26:" >> $@/control
# base64 ${ICON_SOURCE} | ${GAWK} '{print " "$$0;}' >> $@/control
#endif
# Make md5sums.
cd ${PACKAGE_DIR}/data && find . -type f -exec ${MD5SUM} {} \; \
| sed -e 's| \./||' \
> $@/md5sums
${PACKAGE_DIR}/debian-binary:
echo "2.0" > $@
${PACKAGE_DIR}/clean:
rm -rf ${PACKAGE_DIR}/data ${PACKAGE_DIR}/control ${PACKAGE_DIR}/build *.deb
${PACKAGE_DIR}/build: ${PACKAGE_DIR}/debian-binary ${PACKAGE_DIR}/control \
${PACKAGE_DIR}/data
rm -rf $@
mkdir $@
cp ${PACKAGE_DIR}/debian-binary $@/
cd ${PACKAGE_DIR}/control && tar czvf $@/control.tar.gz *
cd ${PACKAGE_DIR}/data && \
COPY_EXTENDED_ATTRIBUTES_DISABLE=true \
COPYFILE_DISABLE=true \
tar cpzvf $@/data.tar.gz *
# Convert GNU ar to BSD ar that debian requires.
# Note: Order of files within ar archive is important!
${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb: ${PACKAGE_DIR}/build
ar -rc $@ $</debian-binary $</control.tar.gz $</data.tar.gz
#sed -e 's|^\([^/]\+\)/ \(.*\)|\1 \2|g' $@tmp > $@fail
#rm -f $@tmp
#mv $@fail $@
.PHONY: data
data: ${PACKAGE_DIR}/data
.PHONY: control
control: ${PACKAGE_DIR}/control
.PHONY: build
build: ${PACKAGE_DIR}/build
.PHONY: clean
clean: ${PACKAGE_DIR}/clean $(EXTRA_CLEAN)
rm -f debian-binary
.PHONY: deb
deb: ${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb
clobber::
rm -rf ${PACKAGE_DIR}/debian_binary ${PACKAGE_DIR}/control \
${PACKAGE_DIR}/data ${PACKAGE_DIR}/build
push:
scp *.deb radare.org:/srv/http/radareorg/cydia/debs
mrproper: clean
rm -rf root
+37
View File
@@ -0,0 +1,37 @@
{
"use": true,
"default": "pipeTest",
"_default": "asciiStack",
"asciiStack": {
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
"pipeline": [
{
"model": "qwen2.5-coder:latest",
"query": "stack frame layout for this function, output a table in ascii art"
}
]
},
"pipeTest": {
"globalQuery": "output in C, respond only with code, no explanations, no markdown",
"pipeline": [
{
"model": "qwen2.5-coder:latest",
"query": "resolve stack variables and arrays size without using malloc"
},
{
"model": "qwen2.5-coder:14b",
"query": "remove boilerplate and unnecessary code, replace goto with if/else blocks"
}
],
"commentedOut": [
{
"model": "qwen2.5-coder:14b",
"query": "use better types and names for variable and arguments"
},
{
"model": "qwen2.5-coder:14b",
"query": "inline all the functions into a single one and make the code more readable"
}
]
}
}
Loaded 100 of 226 files, more files were not shown because too many files have changed in this diff. Show more