1use std::{path::PathBuf, sync::Arc};
8
9use derive_more::{Display, From};
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use crate::{ContentBlock, Error};
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
22#[serde(rename_all = "camelCase")]
23#[schemars(inline)]
24pub struct ToolCall {
25 #[serde(rename = "toolCallId")]
27 pub id: ToolCallId,
28 pub title: String,
30 #[serde(default, skip_serializing_if = "ToolKind::is_default")]
33 pub kind: ToolKind,
34 #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
36 pub status: ToolCallStatus,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
39 pub content: Vec<ToolCallContent>,
40 #[serde(default, skip_serializing_if = "Vec::is_empty")]
43 pub locations: Vec<ToolCallLocation>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub raw_input: Option<serde_json::Value>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub raw_output: Option<serde_json::Value>,
50 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
52 pub meta: Option<serde_json::Value>,
53}
54
55impl ToolCall {
56 pub fn update(&mut self, fields: ToolCallUpdateFields) {
59 if let Some(title) = fields.title {
60 self.title = title;
61 }
62 if let Some(kind) = fields.kind {
63 self.kind = kind;
64 }
65 if let Some(status) = fields.status {
66 self.status = status;
67 }
68 if let Some(content) = fields.content {
69 self.content = content;
70 }
71 if let Some(locations) = fields.locations {
72 self.locations = locations;
73 }
74 if let Some(raw_input) = fields.raw_input {
75 self.raw_input = Some(raw_input);
76 }
77 if let Some(raw_output) = fields.raw_output {
78 self.raw_output = Some(raw_output);
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
90#[serde(rename_all = "camelCase")]
91#[schemars(inline)]
92pub struct ToolCallUpdate {
93 #[serde(rename = "toolCallId")]
95 pub id: ToolCallId,
96 #[serde(flatten)]
98 pub fields: ToolCallUpdateFields,
99 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
101 pub meta: Option<serde_json::Value>,
102}
103
104#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
111#[serde(rename_all = "camelCase")]
112pub struct ToolCallUpdateFields {
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub kind: Option<ToolKind>,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub status: Option<ToolCallStatus>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub title: Option<String>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub content: Option<Vec<ToolCallContent>>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub locations: Option<Vec<ToolCallLocation>>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub raw_input: Option<serde_json::Value>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub raw_output: Option<serde_json::Value>,
134}
135
136impl TryFrom<ToolCallUpdate> for ToolCall {
139 type Error = Error;
140
141 fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
142 let ToolCallUpdate {
143 id,
144 fields:
145 ToolCallUpdateFields {
146 kind,
147 status,
148 title,
149 content,
150 locations,
151 raw_input,
152 raw_output,
153 },
154 meta: _,
155 } = update;
156
157 Ok(Self {
158 id,
159 title: title.ok_or_else(|| {
160 Error::invalid_params()
161 .with_data(serde_json::json!("title is required for a tool call"))
162 })?,
163 kind: kind.unwrap_or_default(),
164 status: status.unwrap_or_default(),
165 content: content.unwrap_or_default(),
166 locations: locations.unwrap_or_default(),
167 raw_input,
168 raw_output,
169 meta: None,
170 })
171 }
172}
173
174impl From<ToolCall> for ToolCallUpdate {
175 fn from(value: ToolCall) -> Self {
176 let ToolCall {
177 id,
178 title,
179 kind,
180 status,
181 content,
182 locations,
183 raw_input,
184 raw_output,
185 meta: _,
186 } = value;
187 Self {
188 id,
189 fields: ToolCallUpdateFields {
190 kind: Some(kind),
191 status: Some(status),
192 title: Some(title),
193 content: Some(content),
194 locations: Some(locations),
195 raw_input,
196 raw_output,
197 },
198 meta: None,
199 }
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
205#[serde(transparent)]
206#[from(Arc<str>, String, &'static str)]
207pub struct ToolCallId(pub Arc<str>);
208
209#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
216#[serde(rename_all = "snake_case")]
217pub enum ToolKind {
218 Read,
220 Edit,
222 Delete,
224 Move,
226 Search,
228 Execute,
230 Think,
232 Fetch,
234 SwitchMode,
236 #[default]
238 #[serde(other)]
239 Other,
240}
241
242impl ToolKind {
243 fn is_default(&self) -> bool {
244 matches!(self, ToolKind::Other)
245 }
246}
247
248#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
254#[serde(rename_all = "snake_case")]
255pub enum ToolCallStatus {
256 #[default]
259 Pending,
260 InProgress,
262 Completed,
264 Failed,
266}
267
268impl ToolCallStatus {
269 fn is_default(&self) -> bool {
270 matches!(self, ToolCallStatus::Pending)
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
281#[serde(tag = "type", rename_all = "snake_case")]
282#[schemars(extend("discriminator" = {"propertyName": "type"}))]
283pub enum ToolCallContent {
284 Content {
286 content: ContentBlock,
288 },
289 Diff {
291 #[serde(flatten)]
293 diff: Diff,
294 },
295 #[serde(rename_all = "camelCase")]
301 Terminal { terminal_id: crate::TerminalId },
302}
303
304impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
305 fn from(content: T) -> Self {
306 ToolCallContent::Content {
307 content: content.into(),
308 }
309 }
310}
311
312impl From<Diff> for ToolCallContent {
313 fn from(diff: Diff) -> Self {
314 ToolCallContent::Diff { diff }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
324#[serde(rename_all = "camelCase")]
325pub struct Diff {
326 pub path: PathBuf,
328 pub old_text: Option<String>,
330 pub new_text: String,
332 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
334 pub meta: Option<serde_json::Value>,
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
344#[serde(rename_all = "camelCase")]
345pub struct ToolCallLocation {
346 pub path: PathBuf,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub line: Option<u32>,
351 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
353 pub meta: Option<serde_json::Value>,
354}