feat(oxide-code): add tool system with bash tool and agent loop - #3
Merged
Conversation
Introduce the tool execution framework and wire it into an agent loop, turning the REPL from a single-shot chat into an autonomous agent that can call tools and act on results. - Add `tool.rs` with `Tool` trait (object-safe via `Pin<Box<dyn Future>>`), `ToolDefinition`, `ToolOutput`, and `ToolRegistry` - Add `tool/bash.rs` with shell execution via `tokio::process::Command`, configurable timeout, and head+tail output truncation (inspired by OpenCode's strategy of preserving both the beginning and end of output) - Add `is_error` field to `ContentBlock::ToolResult` for error signaling - Add `tools` parameter to Anthropic API request and `stream_message` - Replace `send_and_print` with `stream_response` (accumulates both text and tool-use content blocks from SSE) and `agent_turn` (detects tool calls, dispatches, feeds results back, loops until text-only response) - Remove stale `#[expect(dead_code)]` on `ContentBlockInfo` and `Delta` now that `stream_response` uses both types - Update CLAUDE.md crate structure and module organization guidelines
- Bump rust-version to 1.91 (floor_char_boundary, let chains) - Guard truncate_output against barely-over-limit content where the separator would make the result longer than the original - Replace is_false with generic is_default<T: Default + PartialEq> (adopted from kiln) for skip_serializing_if on boolean fields - Reorder test sections in message.rs to mirror production code order (ContentBlock before Message) - Reorder functions in main.rs: stream_response before display helpers, matching call order within agent_turn - Update docs/roadmap.md to reflect shipped tool system
Replace weak assertions that would pass even with buggy code: - Truncation tests: use asymmetric sentinels (HEAD/TAIL markers) instead of uniform data that makes starts_with/ends_with unfalsifiable. Add multi-byte boundary test for floor_char_boundary. - SSE parser tests: verify deserialized field values instead of matching only the enum variant with wildcard (..) patterns. - message.rs: verify all ToolResult fields on deserialization, not just is_error. - oauth.rs: replace vacuous expiresAt > 1000 with tight time-bounded range derived from now_millis + expires_in. - bash execute tests: assert exact output structure instead of loose contains() checks that miss ordering and separator bugs. Add assertion quality guideline to CLAUDE.md testing conventions.
…eout message - Consume tool_uses vec instead of borrowing, eliminating unnecessary id.clone() and input.clone() on each iteration. - Use milliseconds in timeout error message so sub-second timeouts report accurately (was "0s" for 100ms).
Replace legacy claude-sonnet-4-20250514 with claude-sonnet-4-6.
- Merge definitions_includes_all_tools and definitions_serializes_input_schema into a single test. - Remove execute_echo, fully covered by run_valid_command.
hakula139
force-pushed
the
feat/tool-system
branch
from
April 3, 2026 02:49
0269d9d to
6b5ff89
Compare
Use writeln! for exit code and append \n to "(no output)" so all execute() output ends with a trailing newline regardless of which branch appends last. Use indoc! for multiline test assertions.
Keep comments that explain why or clarify non-obvious logic.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add the tool execution framework and agent loop, turning the REPL from a single-shot chat into an autonomous agent that can call tools and act on results. The agent streams responses, detects tool-use content blocks, dispatches to the appropriate tool, feeds the result back, and loops until the LLM produces a text-only response.
tool.rswithTooltrait (object-safe viaPin<Box<dyn Future>>),ToolDefinition,ToolOutput, andToolRegistrytool/bash.rswith shell execution viatokio::process::Command, configurable timeout, and head+tail output truncationis_errorfield toContentBlock::ToolResultfor error signaling back to the LLMtoolsparameter to Anthropic API request viaCreateMessageRequestandstream_messagesend_and_printwithstream_response(accumulates text + tool-use blocks from SSE) andagent_turn(tool dispatch loop with 25-round safety limit)#[expect(dead_code)]onContentBlockInfoandDeltanow thatstream_responseuses both typesDesign Decisions
Pin<Box<dyn Future>>overasync_trait/ enum dispatch: Keeps theTooltrait object-safe forBox<dyn Tool>without adding a dependency. New tools are purely additive — implement the trait and register.BlockAccumulatorstate machine: Tracks in-flight content blocks during SSE streaming. Text deltas are printed in real-time;InputJsonDeltafragments are buffered and parsed onContentBlockStop.Changes
Cargo.tomlprocessandtimefeatures; bump MSRV to 1.91crates/oxide-code/src/tool.rsTooltrait,ToolDefinition,ToolOutput,ToolRegistrycrates/oxide-code/src/tool/bash.rsBashTool— shell execution, timeout, head+tail truncationcrates/oxide-code/src/message.rsis_errorfield toContentBlock::ToolResult; genericis_defaulthelpercrates/oxide-code/src/client/anthropic.rstoolsto API request; simplify empty-to-None withthen_some; strengthen SSE parser tests with field-level assertionscrates/oxide-code/src/config/oauth.rsexpiresAttest assertion to time-bounded rangecrates/oxide-code/src/main.rsagent_turn,stream_response,BlockAccumulator), tool display helpersCLAUDE.mdTest plan
cargo buildcompiles cleanlycargo clippy --all-targets -- -D warnings— zero warningscargo test— 43 tests pass (including multi-byte truncation boundary test)cargo llvm-cov --ignore-filename-regex 'main\.rs'—tool.rs100%,tool/bash.rs95%,message.rs98%bashtool calls and loops correctly