1use std::{path::PathBuf, sync::Arc};
9
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")]
23pub struct ToolCall {
24 #[serde(rename = "toolCallId")]
26 pub id: ToolCallId,
27 pub title: String,
29 #[serde(default, skip_serializing_if = "ToolKind::is_default")]
32 pub kind: ToolKind,
33 #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
35 pub status: ToolCallStatus,
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub content: Vec<ToolCallContent>,
39 #[serde(default, skip_serializing_if = "Vec::is_empty")]
42 pub locations: Vec<ToolCallLocation>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub raw_input: Option<serde_json::Value>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub raw_output: Option<serde_json::Value>,
49}
50
51impl ToolCall {
52 pub fn update(&mut self, fields: ToolCallUpdateFields) {
55 if let Some(title) = fields.title {
56 self.title = title;
57 }
58 if let Some(kind) = fields.kind {
59 self.kind = kind;
60 }
61 if let Some(status) = fields.status {
62 self.status = status;
63 }
64 if let Some(content) = fields.content {
65 self.content = content;
66 }
67 if let Some(locations) = fields.locations {
68 self.locations = locations;
69 }
70 if let Some(raw_input) = fields.raw_input {
71 self.raw_input = Some(raw_input);
72 }
73 if let Some(raw_output) = fields.raw_output {
74 self.raw_output = Some(raw_output);
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
86#[serde(rename_all = "camelCase")]
87pub struct ToolCallUpdate {
88 #[serde(rename = "toolCallId")]
90 pub id: ToolCallId,
91 #[serde(flatten)]
93 pub fields: ToolCallUpdateFields,
94}
95
96#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
101#[serde(rename_all = "camelCase")]
102pub struct ToolCallUpdateFields {
103 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub kind: Option<ToolKind>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub status: Option<ToolCallStatus>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub title: Option<String>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub content: Option<Vec<ToolCallContent>>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub locations: Option<Vec<ToolCallLocation>>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub raw_input: Option<serde_json::Value>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub raw_output: Option<serde_json::Value>,
124}
125
126impl TryFrom<ToolCallUpdate> for ToolCall {
129 type Error = Error;
130
131 fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
132 let ToolCallUpdate {
133 id,
134 fields:
135 ToolCallUpdateFields {
136 kind,
137 status,
138 title,
139 content,
140 locations,
141 raw_input,
142 raw_output,
143 },
144 } = update;
145
146 Ok(Self {
147 id,
148 title: title.ok_or_else(|| {
149 Error::invalid_params()
150 .with_data(serde_json::json!("title is required for a tool call"))
151 })?,
152 kind: kind.unwrap_or_default(),
153 status: status.unwrap_or_default(),
154 content: content.unwrap_or_default(),
155 locations: locations.unwrap_or_default(),
156 raw_input,
157 raw_output,
158 })
159 }
160}
161
162impl From<ToolCall> for ToolCallUpdate {
163 fn from(value: ToolCall) -> Self {
164 let ToolCall {
165 id,
166 title,
167 kind,
168 status,
169 content,
170 locations,
171 raw_input,
172 raw_output,
173 } = value;
174 Self {
175 id,
176 fields: ToolCallUpdateFields {
177 kind: Some(kind),
178 status: Some(status),
179 title: Some(title),
180 content: Some(content),
181 locations: Some(locations),
182 raw_input,
183 raw_output,
184 },
185 }
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
191#[serde(transparent)]
192pub struct ToolCallId(pub Arc<str>);
193
194#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
201#[serde(rename_all = "snake_case")]
202pub enum ToolKind {
203 Read,
205 Edit,
207 Delete,
209 Move,
211 Search,
213 Execute,
215 Think,
217 Fetch,
219 #[default]
221 Other,
222}
223
224impl ToolKind {
225 fn is_default(&self) -> bool {
226 matches!(self, ToolKind::Other)
227 }
228}
229
230#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
236#[serde(rename_all = "snake_case")]
237pub enum ToolCallStatus {
238 #[default]
241 Pending,
242 InProgress,
244 Completed,
246 Failed,
248}
249
250impl ToolCallStatus {
251 fn is_default(&self) -> bool {
252 matches!(self, ToolCallStatus::Pending)
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
263#[serde(tag = "type", rename_all = "snake_case")]
264pub enum ToolCallContent {
265 Content {
267 content: ContentBlock,
269 },
270 Diff {
272 #[serde(flatten)]
274 diff: Diff,
275 },
276}
277
278impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
279 fn from(content: T) -> Self {
280 ToolCallContent::Content {
281 content: content.into(),
282 }
283 }
284}
285
286impl From<Diff> for ToolCallContent {
287 fn from(diff: Diff) -> Self {
288 ToolCallContent::Diff { diff }
289 }
290}
291
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
298#[serde(rename_all = "camelCase")]
299pub struct Diff {
300 pub path: PathBuf,
302 pub old_text: Option<String>,
304 pub new_text: String,
306}
307
308#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
315#[serde(tag = "type", rename_all = "camelCase")]
316pub struct ToolCallLocation {
317 pub path: PathBuf,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub line: Option<u32>,
322}