Skip to main content

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//! - **Declared idempotency keys.** [`IdempotencyPath`] derives the key a
27//!   runtime-defined tool (MCP or wasm) declares, from the input field the
28//!   operator named in the agent file. A hand-written tool overrides
29//!   [`ToolHandler::idempotency_key`] instead; both end up at the same place,
30//!   [`DynTool::idempotency_key`], which is what the runtime deduplicates on.
31//! - **Retry policy.** [`RetryPolicy`] encodes the per-effect rule for
32//!   retrying a failed *live* execution. It classifies; the runtime loop enforces.
33//! - **MCP tools.** Behind the `mcp` cargo feature (on by default), the
34//!   [`mcp`] module connects to an MCP server over stdio and surfaces each of
35//!   its tools as a [`DynTool`], registering alongside native tools. All of the
36//!   MCP dependency surface (the rmcp SDK, a Tokio runtime) is gated behind
37//!   that feature, so the contract layer above still builds with
38//!   `--no-default-features`. MCP stays isolated to that one module by
39//!   design: rmcp/MCP protocol churn is a standing risk.
40//!
41//! # Errors
42//!
43//! A tool's own failure is a [`HandlerError`]. The erased layer's error is a
44//! [`ToolError`], whose [`InvalidInput`](ToolError::InvalidInput) variant (the
45//! model sent malformed arguments, and the handler never ran) is deliberately
46//! distinct from [`Handler`](ToolError::Handler) (the tool ran and failed), so
47//! the runtime loop can route them differently.
48
49mod context;
50mod erased;
51mod error;
52mod handler;
53mod idempotency;
54#[cfg(feature = "mcp")]
55pub mod mcp;
56mod outcome;
57mod registry;
58mod retry;
59
60/// Derives the [`ToolMeta`] impl for a tool struct from `#[tool(...)]`
61/// attributes. See the macro's own documentation for the attribute keys, the
62/// default-name rule, and what it rejects.
63pub use salvor_tools_macros::Tool;
64
65/// The side-effect classification a tool declares, re-exported from
66/// `salvor_core` so a tool author needs only this crate. Both the hand-written
67/// [`ToolMeta::EFFECT`] and the [`Tool`] derive name it through here.
68pub use salvor_core::Effect;
69
70pub use context::ToolCtx;
71pub use erased::{DynTool, ToolDescriptor, TypedTool};
72pub use error::{HandlerError, ToolError};
73pub use handler::{ToolHandler, ToolMeta};
74pub use idempotency::{IdempotencyPath, IdempotencyPathError};
75pub use outcome::{Suspension, ToolOutcome};
76pub use registry::{RegistryError, ToolSet};
77pub use retry::RetryPolicy;