pub use shared_state::SharedState;
use crate::effect::{DisplayOutput, EffectRequest, RiskLevel};
use crate::run::{Artifact, RunContext, RunMetadata};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ToolNamespace {
pub kind: ToolNamespaceKind,
pub id: String,
}
impl ToolNamespace {
pub fn new(kind: ToolNamespaceKind, id: impl Into<String>) -> Self {
Self {
kind,
id: id.into(),
}
}
pub fn local() -> Self {
Self::new(ToolNamespaceKind::Local, "local")
}
pub fn mcp_server(id: impl Into<String>) -> Self {
Self::new(ToolNamespaceKind::McpServer, id)
}
pub fn skill_layer(id: impl Into<String>) -> Self {
Self::new(ToolNamespaceKind::SkillLayer, id)
}
}
impl fmt::Display for ToolNamespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}:{}", self.kind, self.id)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ToolNamespaceKind {
Local,
McpServer,
SkillLayer,
SubAgent,
Custom(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ToolTrustLevel {
Trusted,
Project,
UserInstalled,
External,
Untrusted,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolSource {
pub namespace: ToolNamespace,
pub raw_name: String,
pub display_name: String,
pub trust: ToolTrustLevel,
pub metadata: RunMetadata,
}
impl ToolSource {
pub fn new(
namespace: ToolNamespace,
raw_name: impl Into<String>,
display_name: impl Into<String>,
) -> Self {
Self {
namespace,
raw_name: raw_name.into(),
display_name: display_name.into(),
trust: ToolTrustLevel::External,
metadata: RunMetadata::new(),
}
}
pub fn local(name: impl Into<String>) -> Self {
let name = name.into();
Self {
namespace: ToolNamespace::local(),
raw_name: name.clone(),
display_name: name,
trust: ToolTrustLevel::Trusted,
metadata: RunMetadata::new(),
}
}
pub fn with_trust(mut self, trust: ToolTrustLevel) -> Self {
self.trust = trust;
self
}
pub fn with_metadata(mut self, metadata: RunMetadata) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolSchema {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
pub policy: ToolPolicy,
pub metadata: RunMetadata,
}
impl ToolSchema {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
policy: ToolPolicy::default(),
metadata: RunMetadata::new(),
}
}
pub fn with_policy(mut self, policy: ToolPolicy) -> Self {
self.policy = policy;
self
}
pub fn with_metadata(mut self, metadata: RunMetadata) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolPolicy {
pub side_effects: SideEffectLevel,
pub risk: RiskLevel,
pub requires_confirmation: bool,
pub timeout: Option<Duration>,
pub memory_policy: ToolMemoryPolicy,
}
impl Default for ToolPolicy {
fn default() -> Self {
Self {
side_effects: SideEffectLevel::Pure,
risk: RiskLevel::Low,
requires_confirmation: false,
timeout: None,
memory_policy: ToolMemoryPolicy::Normal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum SideEffectLevel {
Pure,
ReadOnly,
Write,
External,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ToolMemoryPolicy {
#[default]
Normal,
Protected,
}
impl ToolMemoryPolicy {
pub fn is_protected(self) -> bool {
matches!(self, Self::Protected)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolOutput {
pub content: String,
pub display: Option<DisplayOutput>,
pub artifacts: Vec<Artifact>,
pub memory_policy: ToolMemoryPolicy,
pub metadata: RunMetadata,
}
impl ToolOutput {
pub fn text(content: impl Into<String>) -> Self {
Self {
content: content.into(),
display: None,
artifacts: Vec::new(),
memory_policy: ToolMemoryPolicy::Normal,
metadata: RunMetadata::new(),
}
}
pub fn with_display(mut self, display: DisplayOutput) -> Self {
self.display = Some(display);
self
}
pub fn with_artifacts(mut self, artifacts: Vec<Artifact>) -> Self {
self.artifacts = artifacts;
self
}
pub fn with_memory_policy(mut self, policy: ToolMemoryPolicy) -> Self {
self.memory_policy = policy;
self
}
pub fn with_metadata(mut self, metadata: RunMetadata) -> Self {
self.metadata = metadata;
self
}
}
impl From<String> for ToolOutput {
fn from(content: String) -> Self {
Self::text(content)
}
}
impl From<&str> for ToolOutput {
fn from(content: &str) -> Self {
Self::text(content)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ToolResult {
Output(ToolOutput),
Effect(EffectRequest),
}
impl ToolResult {
pub fn output_content(&self) -> Option<&str> {
match self {
Self::Output(output) => Some(&output.content),
Self::Effect(_) => None,
}
}
pub fn content_or_empty(&self) -> &str {
self.output_content().unwrap_or("")
}
}
impl std::fmt::Display for ToolResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Output(output) => f.write_str(&output.content),
Self::Effect(request) => {
write!(
f,
"effect request: {} ({})",
request.description, request.id
)
}
}
}
}
impl From<ToolOutput> for ToolResult {
fn from(output: ToolOutput) -> Self {
Self::Output(output)
}
}
impl From<String> for ToolResult {
fn from(content: String) -> Self {
Self::Output(ToolOutput::text(content))
}
}
impl From<&str> for ToolResult {
fn from(content: &str) -> Self {
Self::Output(ToolOutput::text(content))
}
}
impl PartialEq<str> for ToolResult {
fn eq(&self, other: &str) -> bool {
self.output_content() == Some(other)
}
}
impl PartialEq<&str> for ToolResult {
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl PartialEq<ToolResult> for str {
fn eq(&self, other: &ToolResult) -> bool {
other == self
}
}
impl PartialEq<ToolResult> for &str {
fn eq(&self, other: &ToolResult) -> bool {
other == *self
}
}
impl PartialEq<String> for ToolResult {
fn eq(&self, other: &String) -> bool {
self == other.as_str()
}
}
impl PartialEq<ToolResult> for String {
fn eq(&self, other: &ToolResult) -> bool {
other == self
}
}
#[derive(Debug, Clone, Copy)]
pub struct ToolContext<'a> {
pub run: &'a RunContext,
pub state: &'a SharedState,
pub tool_call_id: &'a str,
pub tool_name: &'a str,
}
impl<'a> ToolContext<'a> {
pub fn new(
run: &'a RunContext,
state: &'a SharedState,
tool_call_id: &'a str,
tool_name: &'a str,
) -> Self {
Self {
run,
state,
tool_call_id,
tool_name,
}
}
}
#[async_trait::async_trait]
pub trait Tool: Send + Sync {
fn schema(&self) -> ToolSchema;
async fn call(
&self,
arguments: serde_json::Value,
context: ToolContext<'_>,
) -> Result<ToolResult, ToolError>;
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ToolError {
#[error("invalid arguments: {0}")]
InvalidArguments(String),
#[error("execution failed: {0}")]
Execution(String),
}
impl From<serde_json::Error> for ToolError {
fn from(err: serde_json::Error) -> Self {
ToolError::InvalidArguments(err.to_string())
}
}
mod shared_state;