Skip to content

Custom tools are now framework-agnostic#123

Merged
Sewer56 merged 5 commits into
mainfrom
custom-tools
May 30, 2026
Merged

Custom tools are now framework-agnostic#123
Sewer56 merged 5 commits into
mainfrom
custom-tools

Conversation

@Sewer56
Copy link
Copy Markdown
Member

@Sewer56 Sewer56 commented May 30, 2026

What's new

You can now write a custom tool once and use it with any ReloadedCode adapter, such as reloaded-code-serdesai or future integrations.

How it works

We moved the shared tool catalog into reloaded-code-core and added a clean registration API. Here's an example using SerdesAI:

use reloaded_code_agents::AgentRuntimeBuilder;
use reloaded_code_core::{
    ToolBuildContext, ToolCatalogEntry, ToolCatalogKind, ToolContext, ToolFactory,
};
use reloaded_code_core::context::ToolPrompt;
use serdes_ai::tools::{RunContext, SchemaBuilder, Tool, ToolDefinition, ToolResult, ToolReturn};
use std::any::Any;
use async_trait::async_trait;

// 1. Define the tool - implement Tool<()> with a definition and call handler
struct EchoTool;

#[async_trait]
impl Tool<()> for EchoTool {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition::new("echo", "Echo a message back")
            .with_parameters(
                SchemaBuilder::new()
                    .string("message", "Message to echo", true)
                    .build()
                    .unwrap(),
            )
    }

    async fn call(&self, _ctx: &RunContext<()>, args: serde_json::Value) -> ToolResult {
        let msg = args["message"].as_str().unwrap_or_default();
        Ok(ToolReturn::text(msg))
    }
}

// 2. Provide name and prompt guidance via ToolContext
struct EchoFactory;
impl ToolContext for EchoFactory {
    fn name(&self) -> &'static str { "echo" }
    fn context(&self) -> ToolPrompt {
        ToolPrompt::Static("Use echo to repeat a message.")
    }
}

// 3. Create the tool at build time via ToolFactory
impl ToolFactory for EchoFactory {
    fn create(&self, _ctx: &ToolBuildContext) -> Box<dyn Any + Send + Sync> {
        Box::new(Box::new(EchoTool) as Box<dyn Tool<()>>)
    }
}

// 4. Register and build
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tools = vec![
        // ...existing tools...
        ToolCatalogEntry::new("echo", ToolCatalogKind::Custom),
    ];

    let runtime = AgentRuntimeBuilder::new()
        .custom_tool(EchoFactory)
        .tools(tools)
        .build()?;
    Ok(())
}

The SerdesAI build layer will look up the factory, and call create(),
downcasting to the framework's tool trait.

If something goes wrong, you get clear errors: UnknownCustomTool if a catalog
entry has no registered factory, or CustomToolDowncastFailed if the type doesn't match.

- Introduce `ToolFactory`, `CustomToolRegistry`, and `ToolCatalogEntry`
  in `reloaded-code-core` so external frameworks can register and
  enumerate custom tools without coupling to agents/serdesai
- Move tool catalog types from `reloaded-code-agents` into
  `reloaded-code-core` so `AgentRuntimeBuilder` can accept core
  catalogs directly
- Wire custom tools through the serdesai `AgentRuntime` builder and
  expose a test-stub factory for framework users
- Update docs, READMEs, and examples across all three crates
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Review Change Stack

Walkthrough

This PR implements a comprehensive custom tool registration system: it adds ToolFactory and registry types to core, extends the tool catalog with ToolCatalogKind::Custom and system-prompt tracking, exposes custom-tool registration on AgentRuntimeBuilder and stores registries in AgentRuntime, and materializes custom tools in SerdesAI's build pipeline (with adapter glue, errors, tests, and extensive documentation updates).

Possibly related PRs

  • Reloaded-Project/ReloadedCode#122: Main PR extends the tool-construction pipeline built around ToolBuildContext (e.g., custom-tool materialization in reloaded-code-serdesai/src/agent_runtime/build.rs) and also adjusts imports to the reloaded_code_core::tool_context::ToolBuildContext path—directly building on PR #122’s ToolBuildContext/refactor.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Custom tools are now framework-agnostic' directly summarizes the main change: introducing a framework-independent custom tool registration system that can be used across different adapters.
Description check ✅ Passed The PR description comprehensively covers the objective (framework-agnostic custom tools), provides clear 'How it works' explanation with crate reorganization, and includes a complete working example with all four registration steps (define tool, provide context, create factory, register and build).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch custom-tools

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/reloaded-code-agents/README.md (1)

368-368: 💤 Low value

Unused link reference definition.

The ToolBuildContext reference is defined but not used in the document. Consider either:

  1. Using it where ToolBuildContext is mentioned (e.g., line 285 in the code comment)
  2. Removing the unused reference
Proposed fix: Remove the unused reference
 [`ToolFactory`]: https://docs.rs/reloaded_code_core/latest/reloaded_code_core/trait.ToolFactory.html
 [`ToolContext`]: https://docs.rs/reloaded_code_core/latest/reloaded_code_core/trait.ToolContext.html
-[`ToolBuildContext`]: https://docs.rs/reloaded_code_core/latest/reloaded_code_core/struct.ToolBuildContext.html
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reloaded-code-agents/README.md` at line 368, Remove the unused Markdown
reference definition for ToolBuildContext in the README: either delete the
unused reference line "`[ToolBuildContext]:
https://docs.rs/reloaded_code_core/latest/reloaded_code_core/struct.ToolBuildContext.html`"
or replace occurrences of the plain text "ToolBuildContext" elsewhere in the
document with the reference-style link (e.g.,
"ToolBuildContext[ToolBuildContext]") so the reference is actually used; look
for the symbol "ToolBuildContext" in the README (e.g., the mention around line
~285) and update or remove the reference accordingly.
docs/src/tools.md (1)

480-481: 💤 Low value

Unused link reference definitions.

The link references for ToolFactory and ToolContext are defined but not used in the document. Consider either:

  1. Using these references in the "Custom tools" section (e.g., change line 121's inline code to a link)
  2. Removing the unused references
Option 1: Use the references

In the custom tools section around line 121, change:

-| [**custom**](`#custom-tools`)          | `ToolFactory`            | User-defined tool registered by the embedder            |
+| [**custom**](`#custom-tools`)          | [`ToolFactory`]          | User-defined tool registered by the embedder            |

And in the code example explanation around line 137, change:

-impl ToolContext for WebSearchFactory {
+impl [`ToolContext`] for WebSearchFactory {
Option 2: Remove the unused references
-[`ToolFactory`]: https://docs.rs/reloaded-code-core/latest/reloaded_code_core/trait.ToolFactory.html
-[`ToolContext`]: https://docs.rs/reloaded-code-core/latest/reloaded_code_core/trait.ToolContext.html
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/tools.md` around lines 480 - 481, The docs define unused reference
links [`ToolFactory`] and [`ToolContext`]; either convert the inline code
mentions of ToolFactory and ToolContext in the "Custom tools" section (replace
plain inline code occurrences with the link references `[ToolFactory]` and
`[ToolContext]` wherever those types are discussed, including the code example
explanation) so the references are used, or remove the two link reference
definitions entirely if you prefer not to link them; update any surrounding
explanatory text to use the chosen form consistently.
src/reloaded-code-core/README.md (1)

382-383: ⚡ Quick win

Remove unused link definitions.

The link definitions for ToolContext::name and ToolPrompt are not referenced anywhere in this document. Static analysis correctly flagged these as unused.

🧹 Remove unused link definitions
 [`ToolContext`]: crate::context::ToolContext
-[`ToolContext::name`]: crate::context::ToolContext::name
-[`ToolPrompt`]: crate::context::ToolPrompt
 [`ToolFactory`]: crate::ToolFactory
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reloaded-code-core/README.md` around lines 382 - 383, Remove the unused
Markdown link definitions for `ToolContext::name` and `ToolPrompt` from the
README (they appear as [`ToolContext::name`]: crate::context::ToolContext::name
and [`ToolPrompt`]: crate::context::ToolPrompt); delete those two link
definition lines and ensure no remaining references to `ToolContext::name` or
`ToolPrompt` exist elsewhere in the document before committing.
src/reloaded-code-serdesai/src/agent_runtime/build.rs (1)

174-183: ⚡ Quick win

Update the # Errors doc to cover custom-tool materialization.

This function now materializes ToolCatalogKind::Custom, so two things are stale:

  • Line 177's "(this function only handles standard tools)" is no longer accurate.
  • The # Errors list omits the newly reachable UnknownCustomTool and CustomToolDowncastFailed variants.

These propagate up to build_agent and AgentBuildContext::build (task.rs Lines 164-174 and 292-302), whose # Errors sections are also missing the new variants.

📝 Proposed doc update
 /// # Errors
 ///
-/// Returns [`AgentBuildError::UnsupportedToolKind`] when the runtime catalog contains an
-/// unrecognized [`ToolCatalogKind`] variant (this function only handles standard tools).
+/// Returns [`AgentBuildError::UnsupportedToolKind`] when the runtime catalog contains a
+/// [`ToolCatalogKind`] variant this adapter cannot materialise.
+///
+/// Returns [`AgentBuildError::UnknownCustomTool`] when a `ToolCatalogKind::Custom` entry has
+/// no factory registered in `custom_tool_registry`.
+///
+/// Returns [`AgentBuildError::CustomToolDowncastFailed`] when a custom tool factory returns a
+/// value that cannot be downcast to `Box<dyn serdes_ai::Tool<()>>`.
 ///
 /// Returns [`AgentBuildError::ToolSettingsValidation`] when resolver creation or settings
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reloaded-code-serdesai/src/agent_runtime/build.rs` around lines 174 -
183, Update the function-level `# Errors` doc in build.rs to remove the phrase
"(this function only handles standard tools)" and add the two newly reachable
AgentBuildError variants resulting from custom-tool materialization:
`AgentBuildError::UnknownCustomTool` and
`AgentBuildError::CustomToolDowncastFailed` (these arise when handling
`ToolCatalogKind::Custom` during tool materialization). Also propagate the same
doc changes to `build_agent` and `AgentBuildContext::build` (they now can return
`UnknownCustomTool` and `CustomToolDowncastFailed`) so their `# Errors` lists
include those variants alongside the existing ones.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/reloaded-code-agents/src/runtime/builder.rs`:
- Around line 12-18: AgentRuntimeBuilder lost its Clone derive which is a
source-incompatible breaking change; restore Clone on AgentRuntimeBuilder
(re-add #[derive(Clone, Debug)] or equivalent) and make CustomToolRegistry
itself cloneable instead of removing builder clonability — either implement
Clone for CustomToolRegistry or change its storage to a shared cloneable type
(e.g., wrap the registry inner data in Arc/Arc<Mutex<_>> or an
Arc<CustomToolRegistryInner>) and update construction/uses accordingly so
AgentRuntimeBuilder can derive/implement Clone again.

---

Nitpick comments:
In `@docs/src/tools.md`:
- Around line 480-481: The docs define unused reference links [`ToolFactory`]
and [`ToolContext`]; either convert the inline code mentions of ToolFactory and
ToolContext in the "Custom tools" section (replace plain inline code occurrences
with the link references `[ToolFactory]` and `[ToolContext]` wherever those
types are discussed, including the code example explanation) so the references
are used, or remove the two link reference definitions entirely if you prefer
not to link them; update any surrounding explanatory text to use the chosen form
consistently.

In `@src/reloaded-code-agents/README.md`:
- Line 368: Remove the unused Markdown reference definition for ToolBuildContext
in the README: either delete the unused reference line "`[ToolBuildContext]:
https://docs.rs/reloaded_code_core/latest/reloaded_code_core/struct.ToolBuildContext.html`"
or replace occurrences of the plain text "ToolBuildContext" elsewhere in the
document with the reference-style link (e.g.,
"ToolBuildContext[ToolBuildContext]") so the reference is actually used; look
for the symbol "ToolBuildContext" in the README (e.g., the mention around line
~285) and update or remove the reference accordingly.

In `@src/reloaded-code-core/README.md`:
- Around line 382-383: Remove the unused Markdown link definitions for
`ToolContext::name` and `ToolPrompt` from the README (they appear as
[`ToolContext::name`]: crate::context::ToolContext::name and [`ToolPrompt`]:
crate::context::ToolPrompt); delete those two link definition lines and ensure
no remaining references to `ToolContext::name` or `ToolPrompt` exist elsewhere
in the document before committing.

In `@src/reloaded-code-serdesai/src/agent_runtime/build.rs`:
- Around line 174-183: Update the function-level `# Errors` doc in build.rs to
remove the phrase "(this function only handles standard tools)" and add the two
newly reachable AgentBuildError variants resulting from custom-tool
materialization: `AgentBuildError::UnknownCustomTool` and
`AgentBuildError::CustomToolDowncastFailed` (these arise when handling
`ToolCatalogKind::Custom` during tool materialization). Also propagate the same
doc changes to `build_agent` and `AgentBuildContext::build` (they now can return
`UnknownCustomTool` and `CustomToolDowncastFailed`) so their `# Errors` lists
include those variants alongside the existing ones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb3467ee-8785-4faa-91b4-e2e77f0bd29f

📥 Commits

Reviewing files that changed from the base of the PR and between 516106c and ed896cf.

📒 Files selected for processing (30)
  • README.MD
  • docs/src/agents.md
  • docs/src/architecture.md
  • docs/src/getting-started.md
  • docs/src/guides/custom-framework.md
  • docs/src/tools.md
  • src/reloaded-code-agents/ARCHITECTURE.md
  • src/reloaded-code-agents/README.md
  • src/reloaded-code-agents/src/lib.rs
  • src/reloaded-code-agents/src/path/resolver.rs
  • src/reloaded-code-agents/src/runtime/builder.rs
  • src/reloaded-code-agents/src/runtime/mod.rs
  • src/reloaded-code-agents/src/runtime/state.rs
  • src/reloaded-code-agents/src/runtime/task.rs
  • src/reloaded-code-core/README.md
  • src/reloaded-code-core/src/context/mod.rs
  • src/reloaded-code-core/src/custom_tool/factory.rs
  • src/reloaded-code-core/src/custom_tool/mod.rs
  • src/reloaded-code-core/src/custom_tool/registry.rs
  • src/reloaded-code-core/src/custom_tool/test_stubs.rs
  • src/reloaded-code-core/src/lib.rs
  • src/reloaded-code-core/src/system_prompt.rs
  • src/reloaded-code-core/src/tool_catalog.rs
  • src/reloaded-code-serdesai/README.md
  • src/reloaded-code-serdesai/src/agent_ext.rs
  • src/reloaded-code-serdesai/src/agent_runtime/build.rs
  • src/reloaded-code-serdesai/src/agent_runtime/mod.rs
  • src/reloaded-code-serdesai/src/agent_runtime/task.rs
  • src/reloaded-code-serdesai/src/agent_runtime/test_stubs.rs
  • src/reloaded-code-serdesai/src/lib.rs
💤 Files with no reviewable changes (1)
  • src/reloaded-code-agents/src/runtime/mod.rs

Comment thread src/reloaded-code-agents/src/runtime/builder.rs
Sewer56 added 4 commits May 30, 2026 18:50
The `[ToolBuildContext]` link reference at the end of the README was
never used - all occurrences of ToolBuildContext in the document are
inside a Rust code block, where Markdown references don't render.
- Removed `[Agents]`, `[agent files]`, `[`ToolFactory`]`, `[`ToolContext`]` link
  definitions that had no corresponding references in the document.
Dropped `[ToolContext::name]` and `[ToolPrompt]` link definitions
from `src/reloaded-code-core/README.md` because they had no
corresponding references elsewhere in the document.
…lization

- Remove stale "(this function only handles standard tools)" note from attach_standard_tools
- Add UnknownCustomTool and CustomToolDowncastFailed variants to attach_standard_tools docs
- Propagate the same two variants to AgentBuildContext::build and build_agent docs
- Use intra-doc link style [ToolCatalogKind::Custom] for consistency
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/reloaded-code-serdesai/src/agent_runtime/task.rs (1)

348-348: ⚡ Quick win

Add targeted tests for custom-tool error paths wired here.

Line 348 introduces the custom-tool registry into build materialization, but this module’s tests don’t currently exercise UnknownCustomTool / CustomToolDowncastFailed paths. Adding two focused tests here would lock in this contract and prevent silent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reloaded-code-serdesai/src/agent_runtime/task.rs` at line 348, Add two
unit tests that exercise the custom-tool error paths invoked via
context.runtime.custom_tool_registry(): one test should simulate a missing
custom tool in the registry and assert that the code returns/propagates
UnknownCustomTool, and the other should simulate a tool present but failing type
conversion and assert CustomToolDowncastFailed is returned; implement these by
constructing a test Runtime/Context with a fake custom_tool_registry() that
either lacks the tool key or returns a boxed object that will fail the downcast,
then call the same build/materialization entrypoint used in production and
assert the exact error variants are produced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/reloaded-code-serdesai/src/agent_runtime/task.rs`:
- Line 348: Add two unit tests that exercise the custom-tool error paths invoked
via context.runtime.custom_tool_registry(): one test should simulate a missing
custom tool in the registry and assert that the code returns/propagates
UnknownCustomTool, and the other should simulate a tool present but failing type
conversion and assert CustomToolDowncastFailed is returned; implement these by
constructing a test Runtime/Context with a fake custom_tool_registry() that
either lacks the tool key or returns a boxed object that will fail the downcast,
then call the same build/materialization entrypoint used in production and
assert the exact error variants are produced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a85273ba-c0ca-41e2-88ca-efe28c8a5e22

📥 Commits

Reviewing files that changed from the base of the PR and between ed896cf and 2dbdcba.

📒 Files selected for processing (5)
  • docs/src/tools.md
  • src/reloaded-code-agents/README.md
  • src/reloaded-code-core/README.md
  • src/reloaded-code-serdesai/src/agent_runtime/build.rs
  • src/reloaded-code-serdesai/src/agent_runtime/task.rs
💤 Files with no reviewable changes (1)
  • src/reloaded-code-core/README.md
✅ Files skipped from review due to trivial changes (1)
  • src/reloaded-code-agents/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/reloaded-code-serdesai/src/agent_runtime/build.rs

@Sewer56 Sewer56 merged commit 9a6b6a9 into main May 30, 2026
20 checks passed
@Sewer56 Sewer56 deleted the custom-tools branch May 30, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant