Skip to main content

oharness_tools/
toolset.rs

1//! `ToolSet` trait (ยง7.1) and `ToolOutcome` / `ToolError` types.
2
3use crate::context::ToolContext;
4use async_trait::async_trait;
5use oharness_core::message::ToolOutput;
6use oharness_core::ToolSpec;
7use serde_json::Value;
8
9#[async_trait]
10pub trait ToolSet: Send + Sync {
11    /// Tool specifications the LLM sees in `CompletionRequest.tools`.
12    fn specs(&self) -> &[ToolSpec];
13
14    /// Execute a tool by name. `name` is guaranteed to be one returned from `specs()`
15    /// by the caller; implementations return `ToolOutcome::ExecutionError` if that
16    /// invariant is violated.
17    async fn execute(&self, name: &str, input: Value, ctx: &ToolContext) -> ToolOutcome;
18}
19
20#[derive(Debug, Clone)]
21pub enum ToolOutcome {
22    Success(ToolOutput),
23    ExecutionError { message: String, recoverable: bool },
24    Denied { reason: String },
25    Cancelled,
26}
27
28impl ToolOutcome {
29    pub fn success_text(s: impl Into<String>) -> Self {
30        ToolOutcome::Success(ToolOutput::text(s))
31    }
32
33    pub fn error(message: impl Into<String>, recoverable: bool) -> Self {
34        ToolOutcome::ExecutionError {
35            message: message.into(),
36            recoverable,
37        }
38    }
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum ToolError {
43    #[error("tool `{0}` not found")]
44    NotFound(String),
45    #[error("invalid input for tool `{name}`: {reason}")]
46    InvalidInput { name: String, reason: String },
47    #[error("tool execution: {0}")]
48    Execution(String),
49    #[error(transparent)]
50    Io(#[from] std::io::Error),
51    #[error("{0}")]
52    Other(String),
53}