salvor_tools/lib.rs
1//! Salvor tools: the typed tool-contract layer for the agent runtime.
2//!
3//! A tool is a typed, effect-classified operation a model may call. This crate
4//! defines what a tool *is* and how the runtime dispatches one; it declares
5//! contracts and performs no IO. Persisting tool-call events and driving the
6//! dispatch is the runtime's job, built on the seam this crate exposes.
7//!
8//! # The layers
9//!
10//! - **The typed contract.** [`ToolMeta`] carries a tool's identity and
11//! [`Effect`](salvor_core::Effect); [`ToolHandler`] adds its typed `Input`,
12//! `Output`, and async [`call`](ToolHandler::call). The split is the seam the
13//! future `#[derive(Tool)]` macro cuts: the macro generates `ToolMeta`, the
14//! user writes `ToolHandler`. See [`ToolMeta`]'s docs for the exact contract.
15//! - **The tool outcome.** [`ToolHandler::call`] returns a [`ToolOutcome`]:
16//! either an `Output`, or a [`Suspension`] that parks the run for a human.
17//! Suspension is a return value in v0.1, not a runtime call.
18//! - **Type-erased dispatch.** [`DynTool`] is the `Value`-in/`Value`-out,
19//! dyn-compatible trait the runtime dispatches through. [`TypedTool`] adapts
20//! any [`ToolHandler`] into a `DynTool`, validating the model's JSON against
21//! the input type before the handler runs. MCP-backed tools (a later task)
22//! implement `DynTool` directly.
23//! - **The registry.** [`ToolSet`] registers tools by name, looks them up, and
24//! enumerates them as [`ToolDescriptor`]s for a model. Duplicate names are a
25//! [`RegistryError`].
26//! - **Retry policy.** [`RetryPolicy`] encodes the per-effect rule for
27//! retrying a failed *live* execution. It classifies; the runtime loop enforces.
28//! - **MCP tools.** Behind the `mcp` cargo feature (on by default), the
29//! [`mcp`] module connects to an MCP server over stdio and surfaces each of
30//! its tools as a [`DynTool`], registering alongside native tools. All of the
31//! MCP dependency surface (the rmcp SDK, a Tokio runtime) is gated behind
32//! that feature, so the contract layer above still builds with
33//! `--no-default-features`. MCP stays isolated to that one module by
34//! design: rmcp/MCP protocol churn is a standing risk.
35//!
36//! # Errors
37//!
38//! A tool's own failure is a [`HandlerError`]. The erased layer's error is a
39//! [`ToolError`], whose [`InvalidInput`](ToolError::InvalidInput) variant (the
40//! model sent malformed arguments, and the handler never ran) is deliberately
41//! distinct from [`Handler`](ToolError::Handler) (the tool ran and failed), so
42//! the runtime loop can route them differently.
43
44mod context;
45mod erased;
46mod error;
47mod handler;
48#[cfg(feature = "mcp")]
49pub mod mcp;
50mod outcome;
51mod registry;
52mod retry;
53
54/// Derives the [`ToolMeta`] impl for a tool struct from `#[tool(...)]`
55/// attributes. See the macro's own documentation for the attribute keys, the
56/// default-name rule, and what it rejects.
57pub use salvor_tools_macros::Tool;
58
59/// The side-effect classification a tool declares, re-exported from
60/// `salvor_core` so a tool author needs only this crate. Both the hand-written
61/// [`ToolMeta::EFFECT`] and the [`Tool`] derive name it through here.
62pub use salvor_core::Effect;
63
64pub use context::ToolCtx;
65pub use erased::{DynTool, ToolDescriptor, TypedTool};
66pub use error::{HandlerError, ToolError};
67pub use handler::{ToolHandler, ToolMeta};
68pub use outcome::{Suspension, ToolOutcome};
69pub use registry::{RegistryError, ToolSet};
70pub use retry::RetryPolicy;