diff --git a/README.MD b/README.MD index c2f926b4..377e4b7a 100644 --- a/README.MD +++ b/README.MD @@ -38,32 +38,30 @@ Add to your `Cargo.toml`: llm-coding-tools-rig = "0.1" ``` -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{ReadTool, WriteTool, GlobTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; -use rig::tool::ToolSet; +use rig::providers::openai; +use rig::completion::Prompt; // Track tools and generate LLM guidance -let mut pb = PreambleBuilder::new(); +let mut pb = PreambleBuilder::::new(); let todos = TodoTools::new(); -let toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(WriteTool::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(BashTool::new())) - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) +let client = openai::Client::from_env(); +let agent = client + .agent("gpt-4o") + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(WriteTool::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) .build(); -// Generate preamble for agent system prompt -let preamble = pb.build(); - -// Use with rig agent: -// let agent = client.agent("gpt-4o") -// .preamble(&preamble) -// .tools(toolset) -// .build(); +// Use the agent +// let response = agent.prompt("List all files").await?; ``` ## Examples diff --git a/src/llm-coding-tools-core/src/lib.rs b/src/llm-coding-tools-core/src/lib.rs index 711607ec..32a835de 100644 --- a/src/llm-coding-tools-core/src/lib.rs +++ b/src/llm-coding-tools-core/src/lib.rs @@ -1,16 +1,5 @@ +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] -//! Core types and utilities for coding tools. -//! -//! This crate provides framework-agnostic building blocks: -//! - [`ToolError`] and [`ToolResult`] for error handling -//! - [`ToolOutput`] for tool responses with truncation metadata -//! - Utility functions for text processing -//! -//! # Features -//! -//! - `async`: Enables async function signatures and async-only modules. -//! - `tokio` (default): Enables async via tokio runtime (implies `async`). -//! When disabled, all operations are synchronous. // Validate feature combinations at compile time #[cfg(all(feature = "async", not(feature = "tokio")))] diff --git a/src/llm-coding-tools-core/src/preamble.rs b/src/llm-coding-tools-core/src/preamble.rs index b5fbd699..1f0cb578 100644 --- a/src/llm-coding-tools-core/src/preamble.rs +++ b/src/llm-coding-tools-core/src/preamble.rs @@ -119,11 +119,13 @@ impl PreambleBuilder { /// // register _my_tool with your tool collection /// ``` /// - /// For example, if working with rig's ToolSet builder: + /// For example, if working with rig's agent builder: /// ```text /// let mut pb = PreambleBuilder::new(); - /// let toolset = ToolSet::builder() - /// .static_tool(pb.track(ReadTool::new())) + /// let agent = client + /// .agent("gpt-4o") + /// .tool(pb.track(ReadTool::new())) + /// .preamble(&pb.build()) /// .build(); /// ``` pub fn track(&mut self, tool: T) -> T { diff --git a/src/llm-coding-tools-rig/README.md b/src/llm-coding-tools-rig/README.md index 0b9ac470..917efe3a 100644 --- a/src/llm-coding-tools-rig/README.md +++ b/src/llm-coding-tools-rig/README.md @@ -29,33 +29,29 @@ llm-coding-tools-rig = "0.1" Minimal runnable agent (requires `OPENAI_API_KEY`): -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{GlobTool, GrepTool, ReadTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; use rig::providers::openai; -use rig::tool::ToolSet; +use rig::client::{ProviderClient, CompletionClient}; +use rig::completion::Prompt; #[tokio::main] async fn main() -> Result<(), Box> { let todos = TodoTools::new(); let mut pb = PreambleBuilder::::new(); - let toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(GrepTool::::new())) - .static_tool(pb.track(BashTool::new())) - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) - .build(); - - let preamble = pb.build(); - + // Build agent with preamble tracking let client = openai::Client::from_env(); let agent = client .agent("gpt-4o") - .preamble(&preamble) - .tools(toolset) + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) // Build preamble after tracking tools .build(); let response = agent @@ -85,7 +81,7 @@ Executes shell commands. File tools come in `absolute::*` (unrestricted) and `allowed::*` (sandboxed) variants: -```rust +```rust,no_run use llm_coding_tools_rig::absolute::{ReadTool, WriteTool}; use llm_coding_tools_rig::allowed::{ReadTool as AllowedReadTool, WriteTool as AllowedWriteTool}; use llm_coding_tools_rig::AllowedPathResolver; diff --git a/src/llm-coding-tools-rig/examples/rig-basic.rs b/src/llm-coding-tools-rig/examples/rig-basic.rs index 7a9fa227..f553768d 100644 --- a/src/llm-coding-tools-rig/examples/rig-basic.rs +++ b/src/llm-coding-tools-rig/examples/rig-basic.rs @@ -1,40 +1,46 @@ -//! PreambleBuilder example - pass-through tracking for ToolSet. +//! PreambleBuilder example - building a complete rig agent. //! //! Demonstrates: -//! - Using PreambleBuilder alongside ToolSet::builder() -//! - Full access to Rig's API (no wrapper limitations) +//! - Using PreambleBuilder with rig's agent builder +//! - Chained .tool() calls for registering tools //! - TodoTools with shared state //! - Generating and using the preamble string //! -//! Run: cargo run --example rig-basic -p llm-coding-tools-rig +//! Run: OPENAI_API_KEY=... cargo run --example rig-basic -p llm-coding-tools-rig use llm_coding_tools_rig::absolute::{GlobTool, GrepTool, ReadTool}; use llm_coding_tools_rig::{BashTool, PreambleBuilder, TodoTools}; -use rig::tool::ToolSet; +use rig::client::{CompletionClient, ProviderClient}; +use rig::completion::Prompt; +use rig::providers::openai; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { // === Create shared state for todos === let todos = TodoTools::new(); // === Create preamble builder to track tools === let mut pb = PreambleBuilder::::new(); - // === Use ToolSet::builder() directly - full Rig API! === - let _toolset = ToolSet::builder() - .static_tool(pb.track(ReadTool::::new())) - .static_tool(pb.track(GlobTool::new())) - .static_tool(pb.track(GrepTool::::new())) - .static_tool(pb.track(BashTool::new())) + // === Build agent with chained .tool() calls === + let client = openai::Client::from_env(); + let agent = client + .agent("gpt-4o") + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) // Todo tools share state for read/write coordination - .static_tool(pb.track(todos.read)) - .static_tool(pb.track(todos.write)) - // Can use any ToolSet method here - dynamic_tool, etc. + .tool(pb.track(todos.read)) + .tool(pb.track(todos.write)) + .preamble(&pb.build()) .build(); - // === Generate preamble string === - let preamble = pb.build(); + // === Use the agent === + let response = agent + .prompt("What files are in the current directory?") + .await?; + println!("{response}"); - // Print the preamble - println!("{preamble}"); + Ok(()) } diff --git a/src/llm-coding-tools-rig/examples/rig-sandboxed.rs b/src/llm-coding-tools-rig/examples/rig-sandboxed.rs index 7c09d29a..7ac370a0 100644 --- a/src/llm-coding-tools-rig/examples/rig-sandboxed.rs +++ b/src/llm-coding-tools-rig/examples/rig-sandboxed.rs @@ -7,11 +7,13 @@ //! - Security-conscious deployments limiting filesystem exposure //! - Project-scoped agents that shouldn't touch system files //! -//! Run: cargo run --example rig-sandboxed -p llm-coding-tools-rig +//! Run: OPENAI_API_KEY=... cargo run --example rig-sandboxed -p llm-coding-tools-rig use llm_coding_tools_rig::allowed::{EditTool, GlobTool, GrepTool, ReadTool, WriteTool}; use llm_coding_tools_rig::{AllowedPathResolver, PreambleBuilder}; -use rig::tool::ToolSet; +use rig::client::{CompletionClient, ProviderClient}; +use rig::completion::Prompt; +use rig::providers::openai; use std::path::PathBuf; #[tokio::main] @@ -40,20 +42,24 @@ async fn main() -> Result<(), Box> { let glob = GlobTool::with_resolver(resolver.clone()); let grep: GrepTool = GrepTool::with_resolver(resolver); - // === Build toolset === + // === Build agent with sandboxed tools === let mut pb = PreambleBuilder::::new(); - let _toolset = ToolSet::builder() - .static_tool(pb.track(read)) - .static_tool(pb.track(write)) - .static_tool(pb.track(edit)) - .static_tool(pb.track(glob)) - .static_tool(pb.track(grep)) + let client = openai::Client::from_env(); + let agent = client + .agent("gpt-4o") + .tool(pb.track(read)) + .tool(pb.track(write)) + .tool(pb.track(edit)) + .tool(pb.track(glob)) + .tool(pb.track(grep)) + .preamble(&pb.build()) .build(); - let preamble = pb.build(); - - // Print the preamble - println!("{preamble}"); + // === Use the agent === + let response = agent + .prompt("List all Rust files in the current directory") + .await?; + println!("{response}"); Ok(()) } diff --git a/src/llm-coding-tools-rig/src/lib.rs b/src/llm-coding-tools-rig/src/lib.rs index bb347d34..a7c4c3d2 100644 --- a/src/llm-coding-tools-rig/src/lib.rs +++ b/src/llm-coding-tools-rig/src/lib.rs @@ -1,21 +1,4 @@ -//! Rig framework Tool implementations for coding tools. -//! -//! This crate provides `rig_core::tool::Tool` implementations wrapping -//! the core operations from [`llm_coding_tools_core`]. -//! -//! # Module Organization -//! -//! - [`absolute`] - Tools requiring absolute paths (no path restriction) -//! - [`allowed`] - Tools restricted to allowed directories -//! - Standalone tools (bash, todo, webfetch) at crate root -//! -//! # Example -//! -//! ```no_run -//! use llm_coding_tools_rig::absolute::ReadTool; -//! use llm_coding_tools_rig::BashTool; -//! ``` - +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] pub mod absolute; diff --git a/src/llm-coding-tools-serdesai/README.md b/src/llm-coding-tools-serdesai/README.md index 016134c9..68a212fb 100644 --- a/src/llm-coding-tools-serdesai/README.md +++ b/src/llm-coding-tools-serdesai/README.md @@ -28,39 +28,47 @@ llm-coding-tools-serdesai = "0.1" ## Quick Start -```rust +Minimal runnable agent (requires `OPENAI_API_KEY`): + +```rust,no_run use llm_coding_tools_serdesai::absolute::{GlobTool, GrepTool, ReadTool}; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::{BashTool, PreambleBuilder, create_todo_tools}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() { - let mut pb = PreambleBuilder::::new(); - let mut registry = ToolRegistry::<()>::new(); - - registry.register(pb.track(ReadTool::::new())); - registry.register(pb.track(GlobTool::new())); - registry.register(pb.track(GrepTool::::new())); - registry.register(pb.track(BashTool::new())); - +async fn main() -> std::result::Result<(), Box> { let (todo_read, todo_write, _state) = create_todo_tools(); - registry.register(pb.track(todo_read)); - registry.register(pb.track(todo_write)); - - let preamble = pb.build(); + let mut pb = PreambleBuilder::::new(); - // Pass `preamble` to your agent's system prompt - // Pass `registry` to your agent's tools + // Build agent with tools - call .system_prompt() last + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + .tool(pb.track(BashTool::new())) + .tool(pb.track(todo_read)) + .tool(pb.track(todo_write)) + .system_prompt(pb.build()) // Last, after tracking all tools + .build(); + + // Run agent with tools + let response = agent + .run("Search for TODO comments in src/", ()) + .await?; + println!("{}", response.output()); + + Ok(()) } ``` -See the [basic example](examples/basic.rs) for a complete working setup. +See the [serdesai-basic example](examples/serdesai-basic.rs) for a complete working setup. ## Usage File tools come in `absolute::*` (unrestricted) and `allowed::*` (sandboxed) variants: -```rust +```rust,no_run use llm_coding_tools_serdesai::absolute::{ReadTool, WriteTool}; use llm_coding_tools_serdesai::allowed::{ReadTool as AllowedReadTool, WriteTool as AllowedWriteTool}; use std::path::PathBuf; @@ -70,22 +78,23 @@ let read = ReadTool::::new(); // Sandboxed access (paths relative to allowed directories) let allowed_paths = vec![PathBuf::from("/home/user/project"), PathBuf::from("/tmp")]; -let sandboxed_read: AllowedReadTool = AllowedReadTool::new(allowed_paths.clone()); -let sandboxed_write = AllowedWriteTool::new(allowed_paths); +let sandboxed_read: AllowedReadTool = AllowedReadTool::new(allowed_paths.clone()).unwrap(); +let sandboxed_write = AllowedWriteTool::new(allowed_paths).unwrap(); ``` Other tools: `BashTool`, `WebFetchTool`, `TaskTool`, `TodoReadTool`, `TodoWriteTool`. -Use `PreambleBuilder` to register tools and pass `pb.build()` to your agent's system prompt. +Use `PreambleBuilder` to track tools and pass `pb.build()` to `.system_prompt()`. +Use `AgentBuilderExt::tool()` to add tools that implement `Tool` to the agent. Context strings are re-exported in `llm_coding_tools_serdesai::context` (e.g., `BASH`, `READ_ABSOLUTE`). ## Examples ```bash -# Basic toolset setup with PreambleBuilder -cargo run --example basic -p llm-coding-tools-serdesai +# Basic agent setup with AgentBuilderExt +cargo run --example serdesai-basic -p llm-coding-tools-serdesai # Sandboxed file access with allowed::* tools -cargo run --example sandboxed -p llm-coding-tools-serdesai +cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai ``` ## License diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs index ef180888..87d23e7a 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-basic.rs @@ -3,62 +3,53 @@ //! Shows: //! - Creating tools individually //! - Using [`PreambleBuilder`] for context generation -//! - Registering tools with [`ToolRegistry`] +//! - Using [`AgentBuilderExt`] to add tools to an agent +//! - Running the agent with tools //! -//! Run: cargo run --example basic -p llm-coding-tools-serdesai +//! Run: OPENAI_API_KEY=... cargo run --example serdesai-basic -p llm-coding-tools-serdesai use llm_coding_tools_serdesai::absolute::{GlobTool, GrepTool, ReadTool}; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::{BashTool, PreambleBuilder, WebFetchTool, create_todo_tools}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() { +async fn main() -> std::result::Result<(), Box> { // === Create preamble builder to track tools === let mut pb = PreambleBuilder::::new(); - // === Create and register tools with ToolRegistry === - let mut registry = ToolRegistry::<()>::new(); - - // File operations - registry.register(pb.track(ReadTool::::new())); - registry.register(pb.track(GlobTool::new())); - registry.register(pb.track(GrepTool::::new())); - - // Shell execution - registry.register(pb.track(BashTool::new())); - - // Web content fetching - registry.register(pb.track(WebFetchTool::new())); - - // Todo tools with shared state + // === Create todo tools with shared state === let (todo_read, todo_write, _state) = create_todo_tools(); - registry.register(pb.track(todo_read)); - registry.register(pb.track(todo_write)); - - // === Generate preamble string === - let preamble = pb.build(); - - // === Print tool definitions from registry === - println!("=== Tools in Registry ({}) ===", registry.len()); - for def in registry.definitions() { - println!(" - {}: {}", def.name, def.description); - } - - // === Print generated preamble === - println!( - "\n=== Generated Preamble ({} chars) ===\n", - preamble.chars().count() - ); - println!("{}", preamble); - // === Integration with serdesAI Agent === - // IMPORTANT: Pass the preamble to your agent's system prompt! - // - // let agent = Agent::builder() - // .model("openai:gpt-4o") - // .system_prompt(&preamble) - // .tools(registry) - // .build()?; - // - // let response = agent.run("Read Cargo.toml", ()).await?; + // === Build agent with tools - call .system_prompt() last === + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + // File operations + .tool(pb.track(ReadTool::::new())) + .tool(pb.track(GlobTool::new())) + .tool(pb.track(GrepTool::::new())) + // Shell execution + .tool(pb.track(BashTool::new())) + // Web content fetching + .tool(pb.track(WebFetchTool::new())) + // Todo tools with shared state + .tool(pb.track(todo_read)) + .tool(pb.track(todo_write)) + // System prompt last (after tracking all tools) + .system_prompt(pb.build()) + .build(); + + // === Print tool info === + println!("=== Agent Ready ({} tools) ===", agent.tools().len()); + + // === Run the agent === + println!("\n=== Running Agent ==="); + let result = agent + .run( + "List the Rust files in the current directory using glob", + (), + ) + .await?; + println!("\n=== Response ===\n{}", result.output()); + + Ok(()) } diff --git a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs index 154fe251..d7e690a7 100644 --- a/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs +++ b/src/llm-coding-tools-serdesai/examples/serdesai-sandboxed.rs @@ -7,14 +7,15 @@ //! - Security-conscious deployments limiting filesystem exposure //! - Project-scoped agents that shouldn't touch system files //! -//! Run: cargo run --example sandboxed -p llm-coding-tools-serdesai +//! Run: OPENAI_API_KEY=... cargo run --example serdesai-sandboxed -p llm-coding-tools-serdesai use llm_coding_tools_serdesai::PreambleBuilder; +use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; use llm_coding_tools_serdesai::allowed::{EditTool, GlobTool, GrepTool, ReadTool, WriteTool}; -use serdes_ai::tools::ToolRegistry; +use serdes_ai::prelude::*; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> std::result::Result<(), Box> { // === Define allowed directories === // // Only these directories (and their subdirectories) will be accessible. @@ -25,29 +26,38 @@ async fn main() -> Result<(), Box> { ]; // === Create tools with allowed paths === - // - // Each tool is initialized with the same set of allowed directories. - // The `allowed` module tools use `AllowedPathResolver` internally. let read: ReadTool = ReadTool::new(allowed_paths.clone())?; let write = WriteTool::new(allowed_paths.clone())?; let edit = EditTool::new(allowed_paths.clone())?; let glob = GlobTool::new(allowed_paths.clone())?; let grep: GrepTool = GrepTool::new(allowed_paths)?; - // === Build registry with preamble tracking === + // === Build agent with sandboxed tools - call .system_prompt() last === let mut pb = PreambleBuilder::::new(); - let mut registry = ToolRegistry::<()>::new(); - - registry.register(pb.track(read)); - registry.register(pb.track(write)); - registry.register(pb.track(edit)); - registry.register(pb.track(glob)); - registry.register(pb.track(grep)); - - let preamble = pb.build(); - - // Print the preamble - println!("{preamble}"); + let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + .tool(pb.track(read)) + .tool(pb.track(write)) + .tool(pb.track(edit)) + .tool(pb.track(glob)) + .tool(pb.track(grep)) + .system_prompt(pb.build()) + .build(); + + // === Print info === + println!( + "=== Sandboxed Agent Ready ({} tools) ===", + agent.tools().len() + ); + println!("Allowed paths:"); + println!(" - Current directory: {:?}", std::env::current_dir()?); + println!(" - Temp directory: {:?}", std::env::temp_dir()); + + // === Run the agent === + println!("\n=== Running Agent ==="); + let result = agent + .run("List all Rust source files in the current directory", ()) + .await?; + println!("\n=== Response ===\n{}", result.output()); Ok(()) } diff --git a/src/llm-coding-tools-serdesai/src/agent_ext.rs b/src/llm-coding-tools-serdesai/src/agent_ext.rs new file mode 100644 index 00000000..f270ceea --- /dev/null +++ b/src/llm-coding-tools-serdesai/src/agent_ext.rs @@ -0,0 +1,86 @@ +//! Extension traits for integrating tools with serdes-ai AgentBuilder. +//! +//! This module provides adapters to use [`Tool`] implementations with +//! serdes-ai's [`AgentBuilder`]. +//! +//! # Example +//! +//! ```no_run +//! use llm_coding_tools_serdesai::absolute::{ReadTool, GlobTool}; +//! use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; +//! use serdes_ai::prelude::*; +//! +//! # fn main() -> std::result::Result<(), Box> { +//! let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? +//! .tool(ReadTool::::new()) +//! .tool(GlobTool::new()) +//! .system_prompt("You are helpful.") +//! .build(); +//! # Ok(()) +//! # } +//! ``` + +use async_trait::async_trait; +use serde_json::Value as JsonValue; +use serdes_ai::agent::ToolExecutor; +use serdes_ai::tools::{RunContext as ToolsRunContext, Tool, ToolError, ToolReturn}; +use serdes_ai::{AgentBuilder, RunContext as AgentRunContext}; + +/// Adapter that wraps a [`Tool`] to implement [`ToolExecutor`]. +/// +/// This bridges the gap between `serdes_ai::tools::Tool` (which uses +/// `tools::RunContext`) and `serdes_ai::agent::ToolExecutor` (which uses +/// `agent::RunContext`). +struct ToolAsExecutor(T); + +#[async_trait] +impl> ToolExecutor for ToolAsExecutor { + async fn execute( + &self, + args: JsonValue, + ctx: &AgentRunContext, + ) -> Result { + // Convert agent::RunContext to tools::RunContext + let tools_ctx = ToolsRunContext::from_arc(ctx.deps.clone(), &ctx.model_name) + .with_run_id(&ctx.run_id) + .with_model_settings(ctx.model_settings.clone()); + + self.0.call(&tools_ctx, args).await + } +} + +/// Extension trait for [`AgentBuilder`] to add tools that implement [`Tool`]. +pub trait AgentBuilderExt { + /// Add a tool that implements the [`Tool`] trait. + /// + /// This is a convenience method that extracts the tool's definition + /// and wraps it with an executor adapter. + /// + /// # Example + /// + /// ```no_run + /// use llm_coding_tools_serdesai::absolute::{ReadTool, GlobTool}; + /// use llm_coding_tools_serdesai::agent_ext::AgentBuilderExt; + /// use serdes_ai::prelude::*; + /// + /// # fn main() -> std::result::Result<(), Box> { + /// let agent = AgentBuilder::<(), String>::from_model("openai:gpt-4o")? + /// .tool(ReadTool::::new()) + /// .tool(GlobTool::new()) + /// .build(); + /// # Ok(()) + /// # } + /// ``` + fn tool + 'static>(self, tool: T) -> Self; +} + +impl AgentBuilderExt for AgentBuilder +where + Deps: Send + Sync + 'static, + Output: Send + Sync + 'static, +{ + fn tool + 'static>(self, tool: T) -> Self { + let definition = tool.definition(); + self.tool_with_executor(definition, ToolAsExecutor(tool)) + } +} diff --git a/src/llm-coding-tools-serdesai/src/convert.rs b/src/llm-coding-tools-serdesai/src/convert.rs index 1e1b8ec0..4ac25563 100644 --- a/src/llm-coding-tools-serdesai/src/convert.rs +++ b/src/llm-coding-tools-serdesai/src/convert.rs @@ -144,9 +144,7 @@ mod tests { #[test] fn invalid_path_error_maps_to_validation_error() { let core_err = CoreError::InvalidPath("not absolute".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); // Use pattern matching - is_validation_error() doesn't exist assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } @@ -154,18 +152,14 @@ mod tests { #[test] fn invalid_pattern_error_maps_to_validation_error() { let core_err = CoreError::InvalidPattern("bad regex".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } #[test] fn out_of_bounds_error_maps_to_validation_error() { let core_err = CoreError::OutOfBounds("offset too large".into()); - let result: Result = - Err(core_error_to_serdes("test_tool", core_err)); - let serdes_err = result.unwrap_err(); + let serdes_err = core_error_to_serdes("test_tool", core_err); assert!(matches!(serdes_err, SerdesError::ValidationFailed { .. })); } diff --git a/src/llm-coding-tools-serdesai/src/lib.rs b/src/llm-coding-tools-serdesai/src/lib.rs index 61db6508..e821d2b8 100644 --- a/src/llm-coding-tools-serdesai/src/lib.rs +++ b/src/llm-coding-tools-serdesai/src/lib.rs @@ -1,24 +1,8 @@ -//! serdesAI framework Tool implementations for coding tools. -//! -//! This crate provides `serdes_ai::Tool` implementations wrapping -//! the core operations from [`llm_coding_tools_core`]. -//! -//! # Module Organization -//! -//! - [`absolute`] - Tools requiring absolute paths (no path restriction) -//! - [`allowed`] - Tools restricted to allowed directories -//! - Standalone tools (bash, todo, webfetch) at crate root -//! -//! # Example -//! -//! ```no_run -//! use llm_coding_tools_serdesai::absolute::ReadTool; -//! use llm_coding_tools_serdesai::BashTool; -//! ``` - +#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))] #![warn(missing_docs)] pub mod absolute; +pub mod agent_ext; pub mod allowed; pub mod bash; pub mod convert;