From 1249ebfadc79b2e3fc5a690f2f823f3775149900 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Wed, 5 Aug 2026 17:57:45 -0400 Subject: [PATCH 1/3] Bump rig to 0.41 so reasoning shows on LocalAI (#745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rig 0.39 only deserialized `delta.reasoning_content`. Providers that stream reasoning as `delta.reasoning` — LocalAI, Cerebras — had it dropped by serde while text and tool calls carried in the same delta worked fine, which is why the panel stayed empty but everything else looked normal. rig 0.40 added the fallback upstream; this takes us to 0.41. The bump is wide. 0.41 splits the classic runtime into rig-agent behind an `agent` feature, replaces Tool::definition with description/parameters across all 37 tools, drops ToolDyn in favour of a concrete struct, and makes Agent::preamble and Agent::model private. Tools move to the context-free PortableTool contract, which is the honest fit — the loop has driven its own dispatch since 4.5h-6 and never used rig's ToolContext. The erased-tool seam is now dirge's own DynTool rather than rig's; it has broken on two consecutive rig releases and does not need to be theirs. AnyAgentInner holds the completion model directly instead of a rig Agent it only ever read the model back out of. Capturing the reasoning then broke Cerebras, which rejects an echoed reasoning_content on the next turn. The field is renamed to `reasoning` at the wire boundary rather than dropped, so the model keeps its own reasoning in context — the same thing @ai-sdk/cerebras does for opencode. Backends that want reasoning_content are untouched: DeepSeek needs it on tool-call turns, and llama.cpp/LocalAI chat templates read it back out of the assistant turn. rmcp was on three versions at once — ours, one from rig's unused rmcp feature, one from agent-client-protocol. That feature is gone, acp goes to 2.0 and rmcp to 3.1, leaving one copy at latest. rusqlite, sysinfo, sha2, jsonschema, base64, compact_str, http and notify-rust go current too; tree-sitter stays at 0.25 because 0.26 does not resolve against the grammar crates. heal.rs listed a third repair it never implemented. The claim is gone, with a note on why the port was skipped: opencode carries an equivalent for DeepSeek only because its own transform lifts reasoning out of the message content, and dirge replays the block in place. --- CHANGELOG.md | 46 ++ Cargo.lock | 538 ++++++++------------ Cargo.toml | 43 +- src/agent/agent_loop/heal.rs | 10 +- src/agent/agent_loop/rig_stream.rs | 8 + src/agent/agent_loop/rig_stream_factory.rs | 60 ++- src/agent/agent_loop/rig_tool.rs | 126 +++-- src/agent/builder/agent_inner.rs | 5 +- src/agent/builder/loop_tools.rs | 4 +- src/agent/builder/reminder_tests.rs | 23 +- src/agent/review.rs | 12 +- src/agent/tools/apply_patch.rs | 101 ++-- src/agent/tools/bash/mod.rs | 35 +- src/agent/tools/bash/tests.rs | 2 +- src/agent/tools/bg_shell.rs | 55 +- src/agent/tools/debug.rs | 93 ++-- src/agent/tools/edit.rs | 41 +- src/agent/tools/edit_lines.rs | 43 +- src/agent/tools/edit_minified.rs | 41 +- src/agent/tools/find_files.rs | 57 +-- src/agent/tools/glob.rs | 59 ++- src/agent/tools/graph.rs | 107 ++-- src/agent/tools/grep.rs | 73 ++- src/agent/tools/issue.rs | 45 +- src/agent/tools/list_dir.rs | 47 +- src/agent/tools/lsp.rs | 97 ++-- src/agent/tools/memory.rs | 51 +- src/agent/tools/plan.rs | 57 +-- src/agent/tools/question.rs | 108 ++-- src/agent/tools/read.rs | 81 ++- src/agent/tools/read_minified.rs | 41 +- src/agent/tools/repo_overview.rs | 49 +- src/agent/tools/semantic/find_callees.rs | 41 +- src/agent/tools/semantic/find_callers.rs | 41 +- src/agent/tools/semantic/find_definition.rs | 35 +- src/agent/tools/semantic/get_symbol_body.rs | 41 +- src/agent/tools/semantic/list_symbols.rs | 39 +- src/agent/tools/session_search.rs | 62 ++- src/agent/tools/skill.rs | 40 +- src/agent/tools/spec.rs | 81 ++- src/agent/tools/task.rs | 40 +- src/agent/tools/task_status.rs | 45 +- src/agent/tools/todo.rs | 54 +- src/agent/tools/webfetch.rs | 53 +- src/agent/tools/websearch.rs | 51 +- src/agent/tools/write.rs | 37 +- src/extras/acp/mod.rs | 19 +- src/extras/mcp/tool.rs | 58 ++- src/extras/mcp_server.rs | 6 +- src/extras/memory_graduation.rs | 7 +- src/extras/session_db.rs | 19 +- src/plugin/hook.rs | 66 +-- src/provider/anthropic_http.rs | 7 +- src/provider/build.rs | 19 +- src/provider/client.rs | 3 + src/provider/compressing_http.rs | 168 ++++++ src/provider/mod.rs | 90 ++-- src/provider/mod_tests.rs | 3 +- src/provider/spawn.rs | 2 +- src/provider/summarize.rs | 4 +- src/semantic/mod.rs | 2 +- src/tests/edit_tests.rs | 2 +- src/ui/plugin_tree.rs | 3 +- src/ui/sysload.rs | 6 +- 64 files changed, 1773 insertions(+), 1529 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82929649..76bc1430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,52 @@ All notable changes to dirge are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed +- Dependency sweep. `rmcp` was on two versions at once — our own 1.7 plus a 2.2 + that `rig`'s unused `rmcp` feature dragged in, and a third from + `agent-client-protocol`. Nothing here ever touched rig's MCP bridge, so that + feature is gone; `agent-client-protocol` moves 0.12 → 2.0 (its types live under + `schema::v1` now, and `respond_with_error` moved from `Dispatch` to the + `Responder` that only the request variant carries); and `rmcp` goes to 3.1, + where `Annotated` is flattened to `ContentBlock`. One rmcp, latest. + + Also current: `rusqlite` 0.31 → 0.40 (no `ToSql for usize`), `sysinfo` + 0.32 → 0.39 (`RefreshKind::new` → `nothing`), `sha2` 0.10 → 0.11 (digest output + dropped `LowerHex`, so the two hex helpers encode explicitly), `jsonschema` + 0.46 → 0.49, `base64` 0.23, `compact_str` 0.10, `http` 1.5, `notify-rust` 4.18. + `tree-sitter` stays at 0.25 — 0.26 does not resolve against the grammar crates. + +### Fixed +- Reasoning is shown again for providers that stream it as `delta.reasoning` + rather than `delta.reasoning_content` (GH #745, reported against LocalAI). + rig 0.39 only deserialized `reasoning_content`, so serde dropped the field and + the panel stayed empty while text and tool calls — carried in the same delta — + worked fine. rig 0.40 added the fallback upstream; this bumps rig to 0.41. + + The bump is not small. 0.41 splits the classic runtime into `rig-agent` behind + an `agent` feature, replaces `Tool::definition` with `description`/`parameters` + across all 37 tools, drops `ToolDyn` in favour of a concrete struct, and makes + `Agent::preamble` and `Agent::model` private. Dirge's tools move to the + context-free `PortableTool` contract, which is the honest fit — the agent loop + has driven its own dispatch since 4.5h-6 and never used rig's `ToolContext`. + The erased-tool seam is now dirge's own `DynTool`; it has broken on two + consecutive rig releases and does not need to be rig's. `AnyAgentInner` holds + the completion model directly instead of a rig `Agent` it only ever read the + model back out of. + +- Cerebras no longer 400s on the turn after a reasoning turn. It streams + reasoning as `delta.reasoning` — the same field LocalAI uses — so the fix + above meant dirge started capturing it and replaying it as `reasoning_content`, + which Cerebras rejects outright. The field is renamed to `reasoning` at the + wire boundary rather than dropped, so the model keeps its own chain of thought + in context on the next turn. Backends that want `reasoning_content` are + untouched: DeepSeek needs it on tool-call turns, and llama.cpp/LocalAI chat + templates read `message.reasoning_content` back out of the assistant turn. + Only the OpenAI Responses API still has the block dropped, because it keys + reasoning to encrypted ids dirge does not retain. + ## [0.21.6] - 2026-08-04 ### Added diff --git a/Cargo.lock b/Cargo.lock index 3e37bfa2..4ceb876d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,44 +41,43 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "0.12.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361ba6627e51de955b10f3c77fb9eb959c85191a236c1c2c84e32f4ff240faf" +checksum = "6d87bc7769eba641753ba5dc52f73ec3765d51022c6753bf040967125ddc86a8" dependencies = [ "agent-client-protocol-derive", "agent-client-protocol-schema", + "async-io", "async-process", "blocking", "futures", "futures-concurrency", - "jsonrpcmsg", - "rmcp", "rustc-hash 2.1.3", + "rustix", "schemars 1.2.1", "serde", "serde_json", "shell-words", - "tokio", - "tokio-util", "tracing", "uuid", + "windows-sys 0.61.2", ] [[package]] name = "agent-client-protocol-derive" -version = "0.11.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabdc9d845d08ec7ed2d0c9de1ae4a1b198301407d55855261572761be90ec9f" +checksum = "3abd4080f51e4f24f5042beb7fb7a66ede29a2dc1c2582c329532e1c27264ddc" dependencies = [ "quote", - "syn 2.0.119", + "syn 3.0.0", ] [[package]] name = "agent-client-protocol-schema" -version = "0.13.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b957d8391ac3933e2a940446171c508d2b8ffc386d8fa7d0b9c936a2575b463e" +checksum = "d5c231915b4ab578c722eca2d1bd7df4d300bfd6cac3b8e9f0d1e3ddc95b187c" dependencies = [ "anyhow", "derive_more", @@ -413,6 +412,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -427,7 +432,7 @@ checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" dependencies = [ "blowfish", "pbkdf2", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -767,7 +772,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -776,7 +781,7 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.119", @@ -821,8 +826,21 @@ dependencies = [ "itoa", "rustversion", "ryu", + "static_assertions", +] + +[[package]] +name = "compact_str" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fcda08c33bb58b97008b2cdada6622500e949e060f5913361763121abd2416" +dependencies = [ + "castaway", + "cfg-if", + "itoa", "serde", "static_assertions", + "zmij", ] [[package]] @@ -1113,6 +1131,16 @@ dependencies = [ "darling_macro 0.23.0", ] +[[package]] +name = "darling" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +dependencies = [ + "darling_core 0.24.0", + "darling_macro 0.24.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -1123,7 +1151,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.119", ] @@ -1136,10 +1164,23 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.119", ] +[[package]] +name = "darling_core" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.0", +] + [[package]] name = "darling_macro" version = "0.20.11" @@ -1163,58 +1204,28 @@ dependencies = [ ] [[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "delegate" -version = "0.13.5" +name = "darling_macro" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" dependencies = [ - "proc-macro2", + "darling_core 0.24.0", "quote", - "syn 2.0.119", -] - -[[package]] -name = "deluxe" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed332aaf752b459088acf3dd4eca323e3ef4b83c70a84ca48fb0ec5305f1488" -dependencies = [ - "deluxe-core", - "deluxe-macros", - "once_cell", - "proc-macro2", - "syn 2.0.119", + "syn 3.0.0", ] [[package]] -name = "deluxe-core" -version = "0.5.0" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddada51c8576df9d6a8450c351ff63042b092c9458b8ac7d20f89cbd0ffd313" -dependencies = [ - "arrayvec", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 2.0.119", -] +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] -name = "deluxe-macros" -version = "0.5.0" +name = "delegate" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87546d9c837f0b7557e47b8bd6eae52c3c223141b76aa233c345c9ab41d9117" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ - "deluxe-core", - "heck 0.4.1", - "if_chain", - "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.119", @@ -1308,12 +1319,12 @@ dependencies = [ "ansi-to-tui", "anyhow", "async-stream", - "base64", + "base64 0.23.1", "bm25", "bytes", "chrono", "clap", - "compact_str", + "compact_str 0.10.0", "crossterm", "csv", "ctor", @@ -1349,7 +1360,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "smallvec 1.15.2", "stop-words 0.10.0", "streaming-iterator", @@ -1484,7 +1495,7 @@ dependencies = [ "ed25519", "rand_core 0.10.1", "serde", - "sha2 0.11.0", + "sha2", "signature", "subtle", "zeroize", @@ -1647,9 +1658,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fancy-regex" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277" +checksum = "476de73bddf2ef8490aa4ee8f1cf40b430bf1d56c48c22080e5186952cd580e6" dependencies = [ "bit-set", "regex-automata", @@ -2060,15 +2071,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -2102,15 +2104,6 @@ dependencies = [ "foldhash 0.2.0", ] -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "hashlink" version = "0.11.1" @@ -2121,10 +2114,13 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.4.1" +name = "hashlink" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248" +dependencies = [ + "hashbrown 0.17.1", +] [[package]] name = "heck" @@ -2192,9 +2188,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -2283,7 +2279,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -2435,12 +2431,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if_chain" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" - [[package]] name = "ignore" version = "0.4.30" @@ -2675,21 +2665,11 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpcmsg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d833a15225c779251e13929203518c2ff26e2fe0f322d584b213f4f4dad37bd" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "jsonschema" -version = "0.46.10" +version = "0.49.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a699d3e77675e6aa4bfffe3b907c8b5f7ed3241f9965bffb25475ad4b08d05" +checksum = "8da60094fc1968bbd091e2cfa004415cb7b19e25e592376e66a64aa832bb6039" dependencies = [ "ahash", "bytecount", @@ -2701,6 +2681,7 @@ dependencies = [ "idna", "itoa", "jsonschema-regex", + "jsonschema-value", "num-cmp", "num-traits", "percent-encoding", @@ -2710,19 +2691,34 @@ dependencies = [ "rustls", "serde", "serde_json", + "strum", "unicode-general-category", "uuid-simd", ] [[package]] name = "jsonschema-regex" -version = "0.46.10" +version = "0.49.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd1086b01b9349fd4ef9a07433965af64c8ce8159abe633a189e4ff817bd13" +checksum = "36539f34ef9e5da418433fbbf7294522d8f930ecf6a540ccd1ec6481c9ff9056" dependencies = [ "regex-syntax", ] +[[package]] +name = "jsonschema-value" +version = "0.49.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc513dfee68c6fe29498451df95a32951ea227801b476f4a1f236433986c891" +dependencies = [ + "ahash", + "bytecount", + "fraction", + "num-cmp", + "num-traits", + "serde_json", +] + [[package]] name = "kasuari" version = "0.4.12" @@ -2804,9 +2800,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db" dependencies = [ "cc", "pkg-config", @@ -3009,15 +3005,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.7", -] - [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -3057,9 +3044,9 @@ dependencies = [ [[package]] name = "notify-rust" -version = "4.17.0" +version = "4.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ff2e74231b72c832d82982193b417f230945be6bdb5575b251d941d31adb00" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" dependencies = [ "futures-lite", "log", @@ -3239,6 +3226,16 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -3298,7 +3295,7 @@ dependencies = [ "elliptic-curve", "primefield", "primeorder", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -3312,7 +3309,7 @@ dependencies = [ "fiat-crypto", "primefield", "primeorder", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -3326,7 +3323,7 @@ dependencies = [ "elliptic-curve", "primefield", "primeorder", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -3342,7 +3339,7 @@ dependencies = [ "futures", "log", "rand 0.10.2", - "sha2 0.11.0", + "sha2", "thiserror 2.0.19", "tokio", "windows 0.62.2", @@ -3541,7 +3538,7 @@ dependencies = [ "pbkdf2", "rand_core 0.10.1", "scrypt", - "sha2 0.11.0", + "sha2", "spki", ] @@ -3620,15 +3617,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "precomputed-hash" version = "0.1.1" @@ -3672,23 +3660,13 @@ dependencies = [ "wnaf", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit", ] [[package]] @@ -3777,17 +3755,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.10.2" @@ -3799,16 +3766,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_core" version = "0.3.1" @@ -3829,9 +3786,6 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] [[package]] name = "rand_core" @@ -3877,7 +3831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ "bitflags 2.13.1", - "compact_str", + "compact_str 0.9.1", "hashbrown 0.17.1", "itertools 0.14.0", "kasuari", @@ -3994,14 +3948,14 @@ dependencies = [ [[package]] name = "referencing" -version = "0.46.10" +version = "0.49.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbf332a2f81899f6836f22c03da73dae8a664c32e3016b84692c23cddadc95d" +checksum = "05915176d843da9ec5c925e5e2631be9aa61b27430acfdd562e79cae8f559725" dependencies = [ "ahash", "fluent-uri 0.4.1", "getrandom 0.3.4", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "itoa", "micromap", "parking_lot", @@ -4044,7 +3998,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -4094,22 +4048,44 @@ dependencies = [ [[package]] name = "rig" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98e2e8f01c4c5bc23f577983634fa4d5244ffb070ea14c23b1ea5bd406e5cac" +checksum = "2ce03971e6115d30ef53fb3244d06718a4e62bbde82c103065600c09459b989a" dependencies = [ + "rig-agent", + "rig-core", +] + +[[package]] +name = "rig-agent" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0796bbf47d7b76670401aac975bc619cf7fba3482b22dfe14992edaa9c2e04" +dependencies = [ + "async-stream", + "fastrand", + "futures", + "http", + "indexmap 2.14.0", "rig-core", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror 2.0.19", + "tokio", + "tracing", + "tracing-futures", ] [[package]] name = "rig-core" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80a4bc7a93b329c4e1a66d5fd211d79990e7331e3c701f057c29f135f548686d" +checksum = "35f5520515ae8f6851adcbc6fde9eea8e96f657418c062e16c82cd81cce44e8e" dependencies = [ "as-any", "async-stream", - "base64", + "base64 0.22.1", "bytes", "eventsource-stream", "fastrand", @@ -4120,12 +4096,10 @@ dependencies = [ "indexmap 2.14.0", "mime", "mime_guess", - "nanoid", "ordered-float", "pin-project-lite", "reqwest", "rig-derive", - "rmcp", "schemars 1.2.1", "serde", "serde_json", @@ -4138,17 +4112,14 @@ dependencies = [ [[package]] name = "rig-derive" -version = "0.39.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5531bfa887b371eab658a92de7db35003370bbeee208ff5e68bbb81a5ae92d3d" +checksum = "eb868fcebdf3ba425e3afad2e4926bb6d9e1188a856843b00bcee2e15c07424f" dependencies = [ "convert_case 0.11.0", - "deluxe", - "indoc", - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", - "serde_json", "syn 2.0.119", ] @@ -4168,12 +4139,12 @@ dependencies = [ [[package]] name = "rmcp" -version = "1.8.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59" +checksum = "094c075f6698deef5a657cf4df6b684dff65157d255978b92b552ec22503f17a" dependencies = [ - "async-trait", - "base64", + "base64 0.23.1", + "bytes", "chrono", "futures", "http", @@ -4191,33 +4162,45 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "uuid", ] [[package]] name = "rmcp-macros" -version = "1.8.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5" +checksum = "737d947bcfd946fae6a179a4ef6487be6dcf25c930c2393856b820f1386e52a6" dependencies = [ - "darling 0.23.0", + "darling 0.24.0", "proc-macro2", "quote", "serde_json", - "syn 2.0.119", + "syn 3.0.0", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.19", ] [[package]] name = "rusqlite" -version = "0.31.0" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323" dependencies = [ "bitflags 2.13.1", "fallible-iterator", "fallible-streaming-iterator", - "hashlink 0.9.1", + "hashlink 0.12.1", "libsqlite3-sys", "smallvec 1.15.2", + "sqlite-wasm-rs", ] [[package]] @@ -4275,7 +4258,7 @@ dependencies = [ "scrypt", "sec1", "sha1", - "sha2 0.11.0", + "sha2", "sha3 0.12.0", "signature", "spki", @@ -4523,7 +4506,7 @@ dependencies = [ "cfg-if", "pbkdf2", "salsa20", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -4662,7 +4645,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ - "base64", + "base64 0.22.1", "bs58", "chrono", "hex", @@ -4720,17 +4703,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.11.0" @@ -4906,6 +4878,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "sse-stream" version = "0.2.4" @@ -4969,7 +4953,7 @@ dependencies = [ "rand_core 0.10.1", "sec1", "sha1", - "sha2 0.11.0", + "sha2", "signature", "ssh-cipher", "ssh-encoding", @@ -5036,12 +5020,6 @@ dependencies = [ "quote", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -5063,7 +5041,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.119", @@ -5119,15 +5097,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.1" +version = "0.39.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", ] [[package]] @@ -5299,7 +5278,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -5347,7 +5325,6 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -5362,18 +5339,12 @@ dependencies = [ "indexmap 2.14.0", "serde_core", "serde_spanned", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.4", + "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -5383,17 +5354,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.6.11", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.25.13+spec-1.1.0" @@ -5401,9 +5361,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", - "winnow 1.0.4", + "winnow", ] [[package]] @@ -5412,7 +5372,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.4", + "winnow", ] [[package]] @@ -6090,16 +6050,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets", -] - [[package]] name = "windows" version = "0.61.3" @@ -6143,26 +6093,14 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets", -] - [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -6174,8 +6112,8 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", @@ -6203,17 +6141,6 @@ dependencies = [ "windows-threading 0.2.1", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -6225,17 +6152,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -6290,15 +6206,6 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -6444,15 +6351,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "1.0.4" @@ -6548,7 +6446,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.4", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -6560,7 +6458,7 @@ version = "5.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -6576,7 +6474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "winnow 1.0.4", + "winnow", "zvariant", ] @@ -6675,7 +6573,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.4", + "winnow", "zvariant_derive", "zvariant_utils", ] @@ -6686,7 +6584,7 @@ version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -6703,5 +6601,5 @@ dependencies = [ "quote", "serde", "syn 2.0.119", - "winnow 1.0.4", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 78248183..66288999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,13 +194,13 @@ libc = "0.2" # - Linux: needs a D-Bus backend. `z` = zbus, a pure-Rust client, so we avoid # a system libdbus build/link dependency. [target.'cfg(target_os = "macos")'.dependencies] -notify-rust = { version = "4", default-features = false } +notify-rust = { version = "4.18", default-features = false } [target.'cfg(target_os = "windows")'.dependencies] -notify-rust = { version = "4", default-features = false } +notify-rust = { version = "4.18", default-features = false } [target.'cfg(target_os = "linux")'.dependencies] -notify-rust = { version = "4", default-features = false, features = ["z"] } +notify-rust = { version = "4.18", default-features = false, features = ["z"] } [dependencies] # CVE-2026 audit (2026-05-27): rig's default features pull a @@ -216,24 +216,31 @@ notify-rust = { version = "4", default-features = false, features = ["z"] } # default → rig-lancedb → lance → tantivy → lru 0.12.5 # ← RUSTSEC-2026-0002 (unsound) # -# Dirge only uses rig's MCP transport (`rmcp` feature) and the core -# provider/agent/completion APIs. By disabling default features we -# drop ~370 transitive crates (881→508 deps) and eliminate all 6 -# cargo-audit findings. +# Dirge uses the core provider/completion APIs plus the classic agent +# runtime. By disabling default features we drop ~370 transitive crates +# (881→508 deps) and eliminate all 6 cargo-audit findings. +# +# `agent` is required: 0.41 split the classic runtime into `rig-agent`, +# and without it `rig::agent::AgentBuilder` and the `rig::tool` traits +# vanish. `rmcp` is inherited from before the split and is a candidate +# for removal — nothing here references rig's MCP bridge, and dropping +# it would also drop the duplicate `rmcp 2.2.0` that rig-agent pulls in +# alongside our own `rmcp 1.7`. +# # Pinned to an exact version, not a caret. rig ships breaking API # changes in patch/minor releases (0.37.0 -> 0.37.1 added a required # `Text.additional_params` field), and `cargo install dirge-agent` # resolves without our Cargo.lock, so a caret would let a published # dirge drift onto an incompatible rig and fail to build (GH #616). # Bump this deliberately when adopting a new rig. -rig = { version = "=0.39.0", default-features = false, features = ["rmcp"] } +rig = { version = "=0.41.0", default-features = false, features = ["agent"] } # Explicit dep to pin feature flags. Without this, rig (our only # dep on rig-core) enables `derive` and `reqwest` via its own # feature resolution, but we want `rustls` explicitly declared so # future readers understand the TLS backend choice is deliberate. # cargo-machete reports this as unused — the package.metadata # block at the top of this file suppresses that false positive. -rig-core = { version = "=0.39.0", default-features = false, features = ["reqwest", "derive"] } +rig-core = { version = "=0.41.0", default-features = false, features = ["reqwest", "derive"] } # Macros for emitting `Stream` from async generator-style # code blocks. Used by `agent_loop::rig_stream` to wrap rig's # `StreamingCompletionResponse` into our pi-style `StreamEvent` @@ -246,7 +253,7 @@ async-stream = "0.3" # undercounts emoji (e.g. ✅ takes 2 cells in most terminals); markdown # tables misaligned the right border when cells contained them. unicode-width = "0.2" -rmcp = { version = "1.7", optional = true, default-features = false, features = [ +rmcp = { version = "3.1", optional = true, default-features = false, features = [ "client", "transport-child-process", "transport-streamable-http-client-reqwest", @@ -292,16 +299,16 @@ rustls = { version = "0.23", default-features = false, features = ["ring"] } # sub-panel. `default-features = false` + only the modules we use # keeps the dep small (no disks / networks / processes — just CPU # + memory polling). -sysinfo = { version = "0.32", default-features = false, features = ["system"] } +sysinfo = { version = "0.39", default-features = false, features = ["system"] } dirs = "6" -compact_str = { version = "0.9", features = ["serde"] } +compact_str = { version = "0.10", features = ["serde"] } smallvec = "1" regex = "1" ignore = "0.4.27" pulldown-cmark = "0.13" include_dir = "0.7" -http = "1" -agent-client-protocol = { version = "0.12.0", optional = true } +http = "1.5" +agent-client-protocol = { version = "2.0.0", optional = true } tree-sitter = { version = "0.25", optional = true } tree-sitter-typescript = { version = "0.23", optional = true } tree-sitter-python = { version = "0.25", optional = true } @@ -314,7 +321,7 @@ tree-sitter-java = { version = "0.23", optional = true } tree-sitter-c = { version = "0.24", optional = true } tree-sitter-cpp = { version = "0.23", optional = true } streaming-iterator = { version = "0.1", optional = true } -jsonschema = { version = "0.46", default-features = false, features = ["resolve-http", "resolve-file", "tls-ring"] } +jsonschema = { version = "0.49", default-features = false, features = ["resolve-http", "resolve-file", "tls-ring"] } janetrs = { version = "0.8", optional = true } html2text = "0.17" indexmap = "2" @@ -326,7 +333,7 @@ lsp-types = { version = "0.97", optional = true } tree-sitter-elixir = { version = "0.3.5", optional = true } tree-sitter-sequel = { version = "0.3", optional = true } tree-sitter-dafny = { version = "0.1", optional = true } -rusqlite = { version = "0.31", features = ["bundled"] } +rusqlite = { version = "0.40", features = ["bundled"] } # DAP (Debug Adapter Protocol) — optional feature for driving # debuggers (lldb-dap, dlv, debugpy, node) to fix crashes instead # of sprinkling print statements. Types are hand-rolled in @@ -349,10 +356,10 @@ russh = { version = "0.62.5", default-features = false, features = ["ring"], opt # sha2 + hex: OCI layer digest verification for the microVM sandbox. # Every blob downloaded from the registry is SHA-256 hashed and compared # against the manifest's declared digest before extraction. -sha2 = "0.10" +sha2 = "0.11" hex = { version = "0.4", optional = true } # base64: decode OpenSSH-format public keys for host-key verification. -base64 = "0.22" +base64 = "0.23" # YAML parser for skill frontmatter (replaces hand-rolled parser in # `src/extras/skills/format.rs`). `yaml-rust2` is the maintained fork # of the abandoned `yaml-rust` crate, fully YAML 1.2 compliant. diff --git a/src/agent/agent_loop/heal.rs b/src/agent/agent_loop/heal.rs index af156495..d04f11a1 100644 --- a/src/agent/agent_loop/heal.rs +++ b/src/agent/agent_loop/heal.rs @@ -8,11 +8,19 @@ //! 1. Shrink oversized tool results (char cap, not token cap) //! 2. Fix unpaired tool calls (drops assistant.tool_calls with no //! matching tool responses + stray tool messages) -//! 3. Stamp missing `reasoning_content` on thinking-mode sessions //! //! The rationale: oversized tool results would 400 the next call //! before the user types. Unpaired tool calls would similarly //! fail API validation. +//! +//! Reasonix's third repair — stamping an empty `reasoning_content` onto +//! assistant turns that lack one — is deliberately NOT ported, and the +//! claim that it was has been removed from this list. opencode carries an +//! equivalent for DeepSeek, but only because its own transform lifts +//! reasoning out of the message content, so it has to put an empty one +//! back. Dirge replays the reasoning block as-is (see +//! `rig_stream_factory::provider_rejects_reasoning_echo`), so there is +//! nothing to restore; the DeepSeek and GLM live smoke tests cover it. use serde_json::Value; diff --git a/src/agent/agent_loop/rig_stream.rs b/src/agent/agent_loop/rig_stream.rs index 5660ee51..28f63db8 100644 --- a/src/agent/agent_loop/rig_stream.rs +++ b/src/agent/agent_loop/rig_stream.rs @@ -505,6 +505,14 @@ where }); } } + // rig 0.40 started surfacing provider output items it does + // not model (reasoning_details and friends) instead of + // dropping them. Dirge builds its own content blocks, so + // there is nothing to render — but the chunk still counted + // as forward motion for the stall detector above, which is + // the behaviour we want. Ignore the payload rather than + // guessing at a block type for it. + Ok(StreamedAssistantContent::Unknown(_)) => {} Err(err) => { let error_msg = err.to_string(); use crate::agent::recovery::classify_error; diff --git a/src/agent/agent_loop/rig_stream_factory.rs b/src/agent/agent_loop/rig_stream_factory.rs index d9731d1a..c2d0ee1c 100644 --- a/src/agent/agent_loop/rig_stream_factory.rs +++ b/src/agent/agent_loop/rig_stream_factory.rs @@ -595,7 +595,7 @@ fn value_to_rig_message_for_provider( } "assistant" => { let blocks = value.get("content").and_then(|c| c.as_array())?; - let include_reasoning = !provider_requires_openai_reasoning_ids(provider_name); + let include_reasoning = !provider_rejects_reasoning_echo(provider_name); let synthesize_call_id = provider_requires_openai_call_ids(provider_name); let assistant_contents: Vec = blocks .iter() @@ -675,7 +675,20 @@ fn value_to_rig_message(value: &Value) -> Option { value_to_rig_message_for_provider(value, None, None) } -fn provider_requires_openai_reasoning_ids(provider_name: Option<&str>) -> bool { +/// Providers that must NOT receive an echoed-back assistant reasoning block. +/// +/// Only `openai` qualifies: its Responses API wants reasoning items keyed by +/// the encrypted ids it issued, which dirge does not retain, so a bare block is +/// rejected and there is no field to rename it to. +/// +/// Every other backend keeps its reasoning. Dropping it would lose the model's +/// own chain of thought from the next turn's context, so a backend that spells +/// the field differently is handled by renaming at the wire boundary instead — +/// see `CompressingHttpClient::rewrite_provider_quirks`, which moves +/// `reasoning_content` to `reasoning` for Cerebras. DeepSeek requires the echo +/// on tool-call turns, and llama.cpp/LocalAI chat templates read +/// `message.reasoning_content` back out of the assistant turn. +fn provider_rejects_reasoning_echo(provider_name: Option<&str>) -> bool { matches!(provider_name, Some(provider) if provider.eq_ignore_ascii_case("openai")) } @@ -1127,6 +1140,49 @@ mod tests { } } + /// GH #745 fallout. Cerebras streams reasoning as `delta.reasoning` — the + /// same non-standard field LocalAI uses. rig 0.39 discarded it, so dirge + /// never had a thinking block to replay; rig 0.40+ keeps it, and replaying + /// it as `reasoning_content` makes Cerebras 400 with + /// `property 'messages.N.assistant.reasoning_content' is unsupported`. + /// + /// The block must still be BUILT here — dropping it would lose the model's + /// reasoning from the next turn's context. The field is renamed at the wire + /// boundary instead (`CompressingHttpClient::rewrite_provider_quirks`). + /// `h7_cerebras_tool_dispatch_completes_round_trip` covers the live round + /// trip; this pins the half that needs no API key. + #[test] + fn reasoning_echo_is_preserved_for_every_backend_but_openai() { + let v = serde_json::json!({ + "role": "assistant", + "content": [ + {"type": "thinking", "text": "let me think"}, + {"type": "text", "text": "the answer"}, + ], + }); + for provider in ["cerebras", "deepseek", "custom", "ollama", "glm"] { + let msg = value_to_rig_message_for_provider(&v, Some(provider), None) + .unwrap_or_else(|| panic!("{provider} must keep the reasoning block")); + match msg { + Message::Assistant { content, .. } => { + assert!( + content + .iter() + .any(|c| matches!(c, AssistantContent::Reasoning(_))), + "{provider} must retain reasoning", + ); + assert!( + content + .iter() + .any(|c| matches!(c, AssistantContent::Text(_))), + "{provider} must retain the assistant's text", + ); + } + _ => panic!("expected Assistant"), + } + } + } + #[test] fn openai_assistant_thinking_only_is_skipped() { let v = serde_json::json!({ diff --git a/src/agent/agent_loop/rig_tool.rs b/src/agent/agent_loop/rig_tool.rs index 731e4e3b..a082562d 100644 --- a/src/agent/agent_loop/rig_tool.rs +++ b/src/agent/agent_loop/rig_tool.rs @@ -31,7 +31,9 @@ use std::pin::Pin; -use rig::tool::{ToolDyn, ToolError}; +use rig::completion::ToolDefinition; +use rig::tool::{IntoToolOutput, PortableTool}; +use rig::wasm_compat::WasmBoxedFuture; use serde_json::Value; use super::result::LoopToolResult; @@ -42,6 +44,72 @@ use super::types::ToolExecutionMode; #[cfg(test)] use std::sync::Arc; +/// Object-safe erased tool surface, replacing the removed +/// `rig::tool::ToolDyn` (rig 0.41 made `ErasedTool` private and +/// exposed only the concrete `DynamicTool`). dirge drives its own +/// agent loop — no rig `ToolContext`/`ToolSet` — so it erases typed +/// tools through this trait instead: any `PortableTool` implements +/// it for free (the same blanket rig 0.39 provided for `ToolDyn`), +/// and hand-rolled tools (MCP, hook-decorated) implement it +/// directly. +pub trait DynTool: Send + Sync { + fn name(&self) -> String; + fn definition(&self) -> WasmBoxedFuture<'_, ToolDefinition>; + fn call(&self, args: String) -> WasmBoxedFuture<'_, Result>; +} + +/// Error surface for an erased tool call, mirroring rig 0.39's +/// `ToolError` (removed in rig 0.41). +#[derive(Debug, thiserror::Error)] +pub enum DynToolError { + #[error("{0}")] + ToolCallError(Box), + #[error("{0}")] + JsonError(#[from] serde_json::Error), +} + +impl DynTool for T +where + T: PortableTool + Send + Sync + 'static, +{ + fn name(&self) -> String { + T::NAME.to_string() + } + + fn definition(&self) -> WasmBoxedFuture<'_, ToolDefinition> { + Box::pin(async move { + ToolDefinition { + name: T::NAME.to_string(), + description: self.description(), + parameters: self.parameters(), + } + }) + } + + fn call(&self, args: String) -> WasmBoxedFuture<'_, Result> { + Box::pin(async move { + // Replicates rig 0.39's blanket `ToolDyn::call`: a bare + // `null` args payload falls back to `{}` so tools whose + // args are all optional still deserialize. + let parsed = match serde_json::from_str::(&args) { + Ok(args) => args, + Err(err) if args.trim() == "null" => serde_json::from_str("{}").map_err(|_| err)?, + Err(err) => return Err(err.into()), + }; + let output = ::call(self, parsed) + .await + .map_err(|e| DynToolError::ToolCallError(Box::new(e)))?; + // Replicates 0.39's output shaping: a plain string comes + // out unquoted; anything else renders as its JSON text. + // `ToolOutput::render` applies exactly that rule. + let tool_output = output + .into_tool_output() + .map_err(|e| DynToolError::ToolCallError(Box::new(e)))?; + Ok(tool_output.render()) + }) + } +} + /// Wraps a `Box` and exposes it as a `LoopTool`. /// /// Built via [`RigToolAdapter::new`] which eagerly resolves the @@ -54,7 +122,7 @@ use std::sync::Arc; /// construction time (Reasonix tools.ts:36-38). The LLM sees flat /// dot-notation keys; `prepare_arguments` re-nests them at dispatch. pub struct RigToolAdapter { - inner: Box, + inner: Box, name: String, description: String, parameters: Value, @@ -89,8 +157,8 @@ impl RigToolAdapter { /// leaves), flattens it and stores the flat variant so /// `prepare_arguments` can re-nest at dispatch (Reasonix /// tools.ts:36-38). - pub async fn new(inner: Box) -> Self { - let def = inner.definition(String::new()).await; + pub async fn new(inner: Box) -> Self { + let def = inner.definition().await; let flat_parameters = match analyze_schema(&def.parameters) { FlattenDecision { should_flatten: true, @@ -121,7 +189,7 @@ impl RigToolAdapter { /// Production callers should use `new`. #[cfg(test)] fn from_parts( - inner: Box, + inner: Box, name: String, description: String, parameters: Value, @@ -221,7 +289,7 @@ impl LoopTool for RigToolAdapter { /// repair layer; this function handles runtime tool errors. If a /// schema error leaks through (defense-in-depth), wrap it in a /// model-readable retry hint rather than leaking raw serde diagnostics. -fn format_tool_error(err: ToolError) -> String { +fn format_tool_error(err: DynToolError) -> String { let raw = err.to_string(); if raw.contains("missing field") || raw.contains("expected") || raw.contains("invalid type") { format!( @@ -238,7 +306,7 @@ fn format_tool_error(err: ToolError) -> String { mod tests { use super::*; use rig::completion::ToolDefinition; - use rig::tool::Tool; + use rig::tool::PortableTool; use serde::{Deserialize, Serialize}; /// Mock rig tool that echoes its input back. Mirrors the @@ -258,24 +326,24 @@ mod tests { Generic(String), } - impl Tool for EchoTool { + impl PortableTool for EchoTool { const NAME: &'static str = "echo"; type Error = EchoError; type Args = EchoArgs; type Output = String; - async fn definition(&self, _prompt: String) -> ToolDefinition { - ToolDefinition { - name: "echo".to_string(), - description: "Echo the input back".to_string(), - parameters: serde_json::json!({ - "type": "object", - "properties": { - "value": {"type": "string"} - }, - "required": ["value"] - }), - } + fn description(&self) -> String { + "Echo the input back".to_string() + } + + fn parameters(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "value": {"type": "string"} + }, + "required": ["value"] + }) } async fn call(&self, args: Self::Args) -> Result { @@ -287,18 +355,18 @@ mod tests { #[derive(Debug, Clone)] struct FailingTool; - impl Tool for FailingTool { + impl PortableTool for FailingTool { const NAME: &'static str = "failing"; type Error = EchoError; type Args = EchoArgs; type Output = String; - async fn definition(&self, _prompt: String) -> ToolDefinition { - ToolDefinition { - name: "failing".to_string(), - description: "Always fails".to_string(), - parameters: serde_json::json!({"type": "object"}), - } + fn description(&self) -> String { + "Always fails".to_string() + } + + fn parameters(&self) -> serde_json::Value { + serde_json::json!({"type": "object"}) } async fn call(&self, _args: Self::Args) -> Result { @@ -444,8 +512,8 @@ mod tests { /// returned by each path must match. #[tokio::test] async fn adapter_matches_rig_path_for_real_dirge_tool() { + use super::DynTool; use crate::agent::tools::ReadTool; - use rig::tool::ToolDyn; // Set up a temp file with known content. Reuses the // same TestDir pattern as fs_atomic tests for cleanup. @@ -465,7 +533,7 @@ mod tests { // both traits are in scope. let tool_a = ReadTool::new(None, None); let rig_args = serde_json::json!({"path": path_str}).to_string(); - let rig_output = ::call(&tool_a, rig_args) + let rig_output = ::call(&tool_a, rig_args) .await .expect("rig direct call should succeed"); diff --git a/src/agent/builder/agent_inner.rs b/src/agent/builder/agent_inner.rs index 709f5aaa..4e953097 100644 --- a/src/agent/builder/agent_inner.rs +++ b/src/agent/builder/agent_inner.rs @@ -48,6 +48,9 @@ pub async fn build_agent_inner( // caller (provider::build_agent) can attach it to AnyAgent for // session-lifecycle hook dispatch. `None` when load failed. Option>, + // rig 0.41 made `Agent::preamble` private, so hand the assembled + // system prompt back instead of reading it off the built agent. + String, ) { // The `plan_file`-keyed gate on edit/write/apply_patch was // removed: prompt-level tool restrictions now live in the @@ -316,7 +319,7 @@ pub async fn build_agent_inner( // `--no-tools`, collects MCP/semantic tools, and applies plugin hooks). // Attaching them here too only duplicated every tool construction and // double-collected MCP tools at startup [dirge-tfip]. - (builder.build(), ToolCache::new(), memory_store) + (builder.build(), ToolCache::new(), memory_store, preamble) } /// Wall-clock bound for the blocking SQLite loads in `build_agent_inner` diff --git a/src/agent/builder/loop_tools.rs b/src/agent/builder/loop_tools.rs index f7a1cd1f..2b625071 100644 --- a/src/agent/builder/loop_tools.rs +++ b/src/agent/builder/loop_tools.rs @@ -264,7 +264,7 @@ pub async fn build_rooted_writer_tools( async fn wrap(inner: T, mode: Option) -> Arc where - T: rig::tool::ToolDyn + 'static, + T: crate::agent::agent_loop::rig_tool::DynTool + 'static, { let adapter = RigToolAdapter::new(Box::new(inner)).await; Arc::new(match mode { @@ -505,7 +505,7 @@ pub async fn build_loop_tools( // is async (RigToolAdapter::new resolves it eagerly). async fn wrap(inner: T, mode: Option) -> Arc where - T: rig::tool::ToolDyn + 'static, + T: crate::agent::agent_loop::rig_tool::DynTool + 'static, { let adapter = RigToolAdapter::new(Box::new(inner)).await; let adapter = match mode { diff --git a/src/agent/builder/reminder_tests.rs b/src/agent/builder/reminder_tests.rs index 160cbec3..2610ec7b 100644 --- a/src/agent/builder/reminder_tests.rs +++ b/src/agent/builder/reminder_tests.rs @@ -536,10 +536,10 @@ async fn build_agent_inner_emits_assembled_preamble() { let client = openai::Client::new("test-key").expect("openai client builds"); let model = client.completion_model("gpt-4o"); - let (agent, _cache, _provider) = + let (_agent, _cache, _provider, agent_preamble) = build_agent_inner(model, &cli, &cfg, &context, "openai", "gpt-4o").await; - let preamble = agent.preamble.unwrap_or_default(); + let preamble = agent_preamble; // SKILLS_GUIDANCE markers (dirge-xxun). assert!( @@ -677,14 +677,14 @@ async fn preamble_lists_global_tier_skills() { let prev_home = std::env::var_os("HOME"); // SAFETY: guarded by HOME_LOCK; restored before the lock drops. unsafe { std::env::set_var("HOME", &home) }; - let (agent, _cache, _provider) = + let (_agent, _cache, _provider, agent_preamble) = build_agent_inner(model, &cli, &cfg, &context, "openai", "gpt-4o").await; match prev_home { Some(h) => unsafe { std::env::set_var("HOME", h) }, None => unsafe { std::env::remove_var("HOME") }, } - let preamble = agent.preamble.unwrap_or_default(); + let preamble = agent_preamble; assert!( preamble.contains("global-preamble-skill"), "preamble must advertise a skill from the global ~/.dirge/skills tier; got:\n{preamble}" @@ -726,25 +726,20 @@ async fn steering_fragment_tracks_active_model_not_cli() { // though the openai client/model and the CLI default are not DeepSeek. let ctx = empty_ctx(); let model = client.completion_model("gpt-4o"); - let (agent, _c, _p) = + let (_agent, _c, _p, agent_preamble) = build_agent_inner(model, &cli, &cfg, &ctx, "deepseek", "deepseek-v4-pro").await; assert!( - agent - .preamble - .unwrap_or_default() - .contains("Plan-Execute-Verify"), + agent_preamble.contains("Plan-Execute-Verify"), "DeepSeek-chat active model must inject the steering fragment" ); // Active model = a non-DeepSeek model → fragment absent. let ctx = empty_ctx(); let model = client.completion_model("gpt-4o"); - let (agent, _c, _p) = build_agent_inner(model, &cli, &cfg, &ctx, "openai", "gpt-4o").await; + let (_agent, _c, _p, agent_preamble) = + build_agent_inner(model, &cli, &cfg, &ctx, "openai", "gpt-4o").await; assert!( - !agent - .preamble - .unwrap_or_default() - .contains("Plan-Execute-Verify"), + !agent_preamble.contains("Plan-Execute-Verify"), "non-DeepSeek active model must NOT inject the steering fragment" ); } diff --git a/src/agent/review.rs b/src/agent/review.rs index 9787f320..4387d92e 100644 --- a/src/agent/review.rs +++ b/src/agent/review.rs @@ -763,11 +763,10 @@ mod tests { .build() .expect("openai client"); let model = client.completion_model("gpt-4o"); - let inner_agent = rig::agent::AgentBuilder::new(model).build(); let provider = Arc::new(RecordingEndProvider::default()); let provider_dyn: Arc = provider.clone(); let agent = AnyAgent::new( - AnyAgentInner::OpenAI(inner_agent), + AnyAgentInner::OpenAI(model), ToolCache::new(), std::time::Duration::from_secs(300), Vec::new(), @@ -871,11 +870,10 @@ mod tests { .build() .unwrap(); let model = client.completion_model("gpt-4o"); - let inner_agent = rig::agent::AgentBuilder::new(model).build(); let provider = Arc::new(RecordingSwitchProvider::default()); let provider_dyn: Arc = provider.clone(); let agent = AnyAgent::new( - AnyAgentInner::OpenAI(inner_agent), + AnyAgentInner::OpenAI(model), ToolCache::new(), std::time::Duration::from_secs(300), Vec::new(), @@ -905,9 +903,8 @@ mod tests { .build() .unwrap(); let model = client.completion_model("gpt-4o"); - let inner_agent = rig::agent::AgentBuilder::new(model).build(); let agent = AnyAgent::new( - AnyAgentInner::OpenAI(inner_agent), + AnyAgentInner::OpenAI(model), ToolCache::new(), std::time::Duration::from_secs(300), Vec::new(), @@ -929,9 +926,8 @@ mod tests { .build() .unwrap(); let model = client.completion_model("gpt-4o"); - let inner_agent = rig::agent::AgentBuilder::new(model).build(); let agent = AnyAgent::new( - AnyAgentInner::OpenAI(inner_agent), + AnyAgentInner::OpenAI(model), ToolCache::new(), std::time::Duration::from_secs(300), Vec::new(), diff --git a/src/agent/tools/apply_patch.rs b/src/agent/tools/apply_patch.rs index 3da5656a..9fb9db77 100644 --- a/src/agent/tools/apply_patch.rs +++ b/src/agent/tools/apply_patch.rs @@ -1,5 +1,4 @@ -use rig::completion::ToolDefinition; -use rig::tool::Tool; +use rig::tool::PortableTool; use serde::Deserialize; use std::path::Path; @@ -197,62 +196,62 @@ async fn apply_rename(path: &str, new_path: &str) -> Result { Ok(format!("renamed {} -> {}", path, new_path)) } -impl Tool for ApplyPatchTool { +impl PortableTool for ApplyPatchTool { const NAME: &'static str = "apply_patch"; type Error = ToolError; type Args = ApplyPatchArgs; type Output = String; - async fn definition(&self, _prompt: String) -> ToolDefinition { - ToolDefinition { - name: "apply_patch".to_string(), - description: crate::agent::agent_loop::tool_input_repair::with_contract_hint( - "apply_patch", - "Apply multiple file operations in a single call. Supports create, update (by exact text match), delete, and rename. Operations execute in order and stop on first failure — prior operations that succeeded remain applied.", - ), - parameters: serde_json::json!({ - "type": "object", - "properties": { - "operations": { - "type": "array", - "description": "Ordered list of file operations to execute", - "items": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": ["create", "update", "delete", "rename"], - "description": "The type of operation" - }, - "path": { - "type": "string", - "description": "Target file path" - }, - "content": { - "type": "string", - "description": "File content (required for create)" - }, - "old_text": { - "type": "string", - "description": "Exact text to find and replace (required for update)" - }, - "new_text": { - "type": "string", - "description": "Replacement text (required for update)" - }, - "new_path": { - "type": "string", - "description": "New file path (required for rename)" - } + fn description(&self) -> String { + crate::agent::agent_loop::tool_input_repair::with_contract_hint( + "apply_patch", + "Apply multiple file operations in a single call. Supports create, update (by exact text match), delete, and rename. Operations execute in order and stop on first failure — prior operations that succeeded remain applied.", + ) + } + + fn parameters(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "operations": { + "type": "array", + "description": "Ordered list of file operations to execute", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["create", "update", "delete", "rename"], + "description": "The type of operation" }, - "required": ["action", "path"] - } + "path": { + "type": "string", + "description": "Target file path" + }, + "content": { + "type": "string", + "description": "File content (required for create)" + }, + "old_text": { + "type": "string", + "description": "Exact text to find and replace (required for update)" + }, + "new_text": { + "type": "string", + "description": "Replacement text (required for update)" + }, + "new_path": { + "type": "string", + "description": "New file path (required for rename)" + } + }, + "required": ["action", "path"] } - }, - "required": ["operations"] - }), - } + } + }, + "required": ["operations"] + }) } async fn call(&self, args: ApplyPatchArgs) -> Result { @@ -597,7 +596,7 @@ mod tests { #[tokio::test] async fn test_definition_has_correct_name() { let tool = ApplyPatchTool::new(None, None); - let def = tool.definition(String::new()).await; + let def = rig::tool::tool_definition(&tool); assert_eq!(def.name, "apply_patch"); } diff --git a/src/agent/tools/bash/mod.rs b/src/agent/tools/bash/mod.rs index 46cecd0f..562ead95 100644 --- a/src/agent/tools/bash/mod.rs +++ b/src/agent/tools/bash/mod.rs @@ -1,5 +1,4 @@ -use rig::completion::ToolDefinition; -use rig::tool::Tool; +use rig::tool::PortableTool; pub(crate) mod check; pub(crate) mod exec; @@ -87,31 +86,31 @@ impl BashTool { } } -impl Tool for BashTool { +impl PortableTool for BashTool { const NAME: &'static str = "bash"; type Error = ToolError; type Args = BashArgs; type Output = String; - async fn definition(&self, _prompt: String) -> ToolDefinition { - ToolDefinition { - name: "bash".to_string(), - description: with_contract_hint( + fn description(&self) -> String { + with_contract_hint( "bash", &("Execute a bash command in the current working directory. Returns stdout and stderr.".to_owned() + cfg!(feature = "experimental-ui-computer-use").then_some("\n\nDesktop automation: prefix commands with `computer:` to control the desktop GUI. Actions: `computer:open_url ` (opens in browser), `computer:screenshot` (captures screen), `computer:type `, `computer:key `, `computer:click