1use serde::{Deserialize, Serialize};
2
3use crate::InvocationContext;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ToolEffectKind {
9 ReadFile,
10 ReadSecret,
11 WriteFile,
12 DeleteFile,
13 RunProcess,
14 NetworkRequest,
15 SendMessage,
16 SpendMoney,
17 Deploy,
18 ModifyCredential,
19 PersistMemory,
20 PublishContent,
21 ScheduleTask,
22 GenerateMedia,
23 IntrospectRuntime,
24 DelegateWork,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum EffectReversibility {
31 Reversible,
32 PartiallyReversible,
33 Irreversible,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum EffectConfirmation {
40 Never,
41 OnRisk,
42 Always,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum DryRunSupport {
49 NotSupported,
50 Supported,
51 RequiredBeforeExecute,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct ToolEffect {
57 pub kind: ToolEffectKind,
59 #[serde(default, skip_serializing_if = "String::is_empty")]
61 pub target: String,
62 pub reversibility: EffectReversibility,
64 pub confirmation: EffectConfirmation,
66 pub dry_run: DryRunSupport,
68}
69
70impl ToolEffect {
71 #[must_use]
72 pub const fn new(kind: ToolEffectKind) -> Self {
73 Self {
74 kind,
75 target: String::new(),
76 reversibility: kind.default_reversibility(),
77 confirmation: kind.default_confirmation(),
78 dry_run: DryRunSupport::NotSupported,
79 }
80 }
81
82 #[must_use]
83 pub fn with_target(mut self, target: impl Into<String>) -> Self {
84 self.target = target.into();
85 self
86 }
87
88 #[must_use]
89 pub const fn with_reversibility(mut self, reversibility: EffectReversibility) -> Self {
90 self.reversibility = reversibility;
91 self
92 }
93
94 #[must_use]
95 pub const fn with_confirmation(mut self, confirmation: EffectConfirmation) -> Self {
96 self.confirmation = confirmation;
97 self
98 }
99
100 #[must_use]
101 pub const fn with_dry_run(mut self, dry_run: DryRunSupport) -> Self {
102 self.dry_run = dry_run;
103 self
104 }
105
106 #[must_use]
107 pub const fn is_mutating(&self) -> bool {
108 self.kind.is_mutating()
109 }
110
111 #[must_use]
112 pub fn label(&self) -> String {
113 if self.target.is_empty() {
114 format!("{:?}", self.kind)
115 } else {
116 format!("{:?}:{}", self.kind, self.target)
117 }
118 }
119}
120
121impl ToolEffectKind {
122 #[must_use]
123 pub const fn is_mutating(self) -> bool {
124 !matches!(
125 self,
126 Self::ReadFile | Self::ReadSecret | Self::NetworkRequest | Self::IntrospectRuntime
127 )
128 }
129
130 const fn default_reversibility(self) -> EffectReversibility {
131 match self {
132 Self::ReadFile | Self::NetworkRequest | Self::IntrospectRuntime => {
133 EffectReversibility::Reversible
134 }
135 Self::WriteFile
136 | Self::RunProcess
137 | Self::PersistMemory
138 | Self::ScheduleTask
139 | Self::GenerateMedia
140 | Self::DelegateWork => EffectReversibility::PartiallyReversible,
141 Self::ReadSecret
142 | Self::DeleteFile
143 | Self::SendMessage
144 | Self::SpendMoney
145 | Self::Deploy
146 | Self::ModifyCredential
147 | Self::PublishContent => EffectReversibility::Irreversible,
148 }
149 }
150
151 const fn default_confirmation(self) -> EffectConfirmation {
152 match self {
153 Self::ReadFile | Self::NetworkRequest | Self::IntrospectRuntime => {
154 EffectConfirmation::OnRisk
155 }
156 _ => EffectConfirmation::Always,
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
162pub struct ToolCapabilities {
163 pub emits_progress: bool,
165 pub emits_observer_text: bool,
167 pub background_safe: bool,
169 #[serde(default, skip_serializing_if = "Vec::is_empty")]
171 pub effects: Vec<ToolEffect>,
172}
173
174impl ToolCapabilities {
175 #[must_use]
176 pub fn with_effect(mut self, effect: ToolEffect) -> Self {
177 self.effects.push(effect);
178 self
179 }
180
181 #[must_use]
182 pub fn with_effects(mut self, effects: impl IntoIterator<Item = ToolEffect>) -> Self {
183 self.effects.extend(effects);
184 self
185 }
186}
187
188pub trait ToolRuntime: Send + Sync {
193 fn invocation(&self) -> &InvocationContext;
195
196 fn emit_progress(&self, message: &str);
198
199 fn emit_observer(&self, source: Option<&str>, content: &str);
202}