use serde::{Deserialize, Serialize};
use crate::InvocationContext;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolEffectKind {
ReadFile,
ReadSecret,
WriteFile,
DeleteFile,
RunProcess,
NetworkRequest,
SendMessage,
SpendMoney,
Deploy,
ModifyCredential,
PersistMemory,
PublishContent,
ScheduleTask,
GenerateMedia,
IntrospectRuntime,
DelegateWork,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectReversibility {
Reversible,
PartiallyReversible,
Irreversible,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectConfirmation {
Never,
OnRisk,
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DryRunSupport {
NotSupported,
Supported,
RequiredBeforeExecute,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolEffect {
pub kind: ToolEffectKind,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub target: String,
pub reversibility: EffectReversibility,
pub confirmation: EffectConfirmation,
pub dry_run: DryRunSupport,
}
impl ToolEffect {
#[must_use]
pub const fn new(kind: ToolEffectKind) -> Self {
Self {
kind,
target: String::new(),
reversibility: kind.default_reversibility(),
confirmation: kind.default_confirmation(),
dry_run: DryRunSupport::NotSupported,
}
}
#[must_use]
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = target.into();
self
}
#[must_use]
pub const fn with_reversibility(mut self, reversibility: EffectReversibility) -> Self {
self.reversibility = reversibility;
self
}
#[must_use]
pub const fn with_confirmation(mut self, confirmation: EffectConfirmation) -> Self {
self.confirmation = confirmation;
self
}
#[must_use]
pub const fn with_dry_run(mut self, dry_run: DryRunSupport) -> Self {
self.dry_run = dry_run;
self
}
#[must_use]
pub const fn is_mutating(&self) -> bool {
self.kind.is_mutating()
}
#[must_use]
pub fn label(&self) -> String {
if self.target.is_empty() {
format!("{:?}", self.kind)
} else {
format!("{:?}:{}", self.kind, self.target)
}
}
}
impl ToolEffectKind {
#[must_use]
pub const fn is_mutating(self) -> bool {
!matches!(
self,
Self::ReadFile | Self::ReadSecret | Self::NetworkRequest | Self::IntrospectRuntime
)
}
const fn default_reversibility(self) -> EffectReversibility {
match self {
Self::ReadFile | Self::NetworkRequest | Self::IntrospectRuntime => {
EffectReversibility::Reversible
}
Self::WriteFile
| Self::RunProcess
| Self::PersistMemory
| Self::ScheduleTask
| Self::GenerateMedia
| Self::DelegateWork => EffectReversibility::PartiallyReversible,
Self::ReadSecret
| Self::DeleteFile
| Self::SendMessage
| Self::SpendMoney
| Self::Deploy
| Self::ModifyCredential
| Self::PublishContent => EffectReversibility::Irreversible,
}
}
const fn default_confirmation(self) -> EffectConfirmation {
match self {
Self::ReadFile | Self::NetworkRequest | Self::IntrospectRuntime => {
EffectConfirmation::OnRisk
}
_ => EffectConfirmation::Always,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct ToolCapabilities {
pub emits_progress: bool,
pub emits_observer_text: bool,
pub background_safe: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub effects: Vec<ToolEffect>,
}
impl ToolCapabilities {
#[must_use]
pub fn with_effect(mut self, effect: ToolEffect) -> Self {
self.effects.push(effect);
self
}
#[must_use]
pub fn with_effects(mut self, effects: impl IntoIterator<Item = ToolEffect>) -> Self {
self.effects.extend(effects);
self
}
}
pub trait ToolRuntime: Send + Sync {
fn invocation(&self) -> &InvocationContext;
fn emit_progress(&self, message: &str);
fn emit_observer(&self, source: Option<&str>, content: &str);
}