1use std::{path::PathBuf, sync::Arc};
8
9use derive_more::{Display, From};
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
13
14use crate::{ContentBlock, Error, IntoOption, Meta, SkipListener, TerminalId};
15
16#[serde_as]
23#[skip_serializing_none]
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
25#[serde(rename_all = "camelCase")]
26#[non_exhaustive]
27pub struct ToolCall {
28 pub tool_call_id: ToolCallId,
30 pub title: String,
32 #[serde(default, skip_serializing_if = "ToolKind::is_default")]
35 pub kind: ToolKind,
36 #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
38 pub status: ToolCallStatus,
39 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
41 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
42 #[serde(default, skip_serializing_if = "Vec::is_empty")]
43 pub content: Vec<ToolCallContent>,
44 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
47 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub locations: Vec<ToolCallLocation>,
50 pub raw_input: Option<serde_json::Value>,
52 pub raw_output: Option<serde_json::Value>,
54 #[serde(rename = "_meta")]
60 pub meta: Option<Meta>,
61}
62
63impl ToolCall {
64 #[must_use]
65 pub fn new(tool_call_id: impl Into<ToolCallId>, title: impl Into<String>) -> Self {
66 Self {
67 tool_call_id: tool_call_id.into(),
68 title: title.into(),
69 kind: ToolKind::default(),
70 status: ToolCallStatus::default(),
71 content: Vec::default(),
72 locations: Vec::default(),
73 raw_input: None,
74 raw_output: None,
75 meta: None,
76 }
77 }
78
79 #[must_use]
82 pub fn kind(mut self, kind: ToolKind) -> Self {
83 self.kind = kind;
84 self
85 }
86
87 #[must_use]
89 pub fn status(mut self, status: ToolCallStatus) -> Self {
90 self.status = status;
91 self
92 }
93
94 #[must_use]
96 pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
97 self.content = content;
98 self
99 }
100
101 #[must_use]
104 pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
105 self.locations = locations;
106 self
107 }
108
109 #[must_use]
111 pub fn raw_input(mut self, raw_input: impl IntoOption<serde_json::Value>) -> Self {
112 self.raw_input = raw_input.into_option();
113 self
114 }
115
116 #[must_use]
118 pub fn raw_output(mut self, raw_output: impl IntoOption<serde_json::Value>) -> Self {
119 self.raw_output = raw_output.into_option();
120 self
121 }
122
123 #[must_use]
129 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
130 self.meta = meta.into_option();
131 self
132 }
133
134 pub fn update(&mut self, fields: ToolCallUpdateFields) {
137 if let Some(title) = fields.title {
138 self.title = title;
139 }
140 if let Some(kind) = fields.kind {
141 self.kind = kind;
142 }
143 if let Some(status) = fields.status {
144 self.status = status;
145 }
146 if let Some(content) = fields.content {
147 self.content = content;
148 }
149 if let Some(locations) = fields.locations {
150 self.locations = locations;
151 }
152 if let Some(raw_input) = fields.raw_input {
153 self.raw_input = Some(raw_input);
154 }
155 if let Some(raw_output) = fields.raw_output {
156 self.raw_output = Some(raw_output);
157 }
158 }
159}
160
161#[skip_serializing_none]
168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
169#[serde(rename_all = "camelCase")]
170#[non_exhaustive]
171pub struct ToolCallUpdate {
172 pub tool_call_id: ToolCallId,
174 #[serde(flatten)]
176 pub fields: ToolCallUpdateFields,
177 #[serde(rename = "_meta")]
183 pub meta: Option<Meta>,
184}
185
186impl ToolCallUpdate {
187 #[must_use]
188 pub fn new(tool_call_id: impl Into<ToolCallId>, fields: ToolCallUpdateFields) -> Self {
189 Self {
190 tool_call_id: tool_call_id.into(),
191 fields,
192 meta: None,
193 }
194 }
195
196 #[must_use]
202 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
203 self.meta = meta.into_option();
204 self
205 }
206}
207
208#[serde_as]
215#[skip_serializing_none]
216#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
217#[serde(rename_all = "camelCase")]
218#[non_exhaustive]
219pub struct ToolCallUpdateFields {
220 #[serde_as(deserialize_as = "DefaultOnError")]
222 #[schemars(extend("x-deserialize-default-on-error" = true))]
223 #[serde(default)]
224 pub kind: Option<ToolKind>,
225 #[serde_as(deserialize_as = "DefaultOnError")]
227 #[schemars(extend("x-deserialize-default-on-error" = true))]
228 #[serde(default)]
229 pub status: Option<ToolCallStatus>,
230 pub title: Option<String>,
232 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
234 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
235 #[serde(default)]
236 pub content: Option<Vec<ToolCallContent>>,
237 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
239 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
240 #[serde(default)]
241 pub locations: Option<Vec<ToolCallLocation>>,
242 pub raw_input: Option<serde_json::Value>,
244 pub raw_output: Option<serde_json::Value>,
246}
247
248impl ToolCallUpdateFields {
249 #[must_use]
250 pub fn new() -> Self {
251 Self::default()
252 }
253
254 #[must_use]
256 pub fn kind(mut self, kind: impl IntoOption<ToolKind>) -> Self {
257 self.kind = kind.into_option();
258 self
259 }
260
261 #[must_use]
263 pub fn status(mut self, status: impl IntoOption<ToolCallStatus>) -> Self {
264 self.status = status.into_option();
265 self
266 }
267
268 #[must_use]
270 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
271 self.title = title.into_option();
272 self
273 }
274
275 #[must_use]
277 pub fn content(mut self, content: impl IntoOption<Vec<ToolCallContent>>) -> Self {
278 self.content = content.into_option();
279 self
280 }
281
282 #[must_use]
284 pub fn locations(mut self, locations: impl IntoOption<Vec<ToolCallLocation>>) -> Self {
285 self.locations = locations.into_option();
286 self
287 }
288
289 #[must_use]
291 pub fn raw_input(mut self, raw_input: impl IntoOption<serde_json::Value>) -> Self {
292 self.raw_input = raw_input.into_option();
293 self
294 }
295
296 #[must_use]
298 pub fn raw_output(mut self, raw_output: impl IntoOption<serde_json::Value>) -> Self {
299 self.raw_output = raw_output.into_option();
300 self
301 }
302}
303
304impl TryFrom<ToolCallUpdate> for ToolCall {
307 type Error = Error;
308
309 fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
310 let ToolCallUpdate {
311 tool_call_id,
312 fields:
313 ToolCallUpdateFields {
314 kind,
315 status,
316 title,
317 content,
318 locations,
319 raw_input,
320 raw_output,
321 },
322 meta,
323 } = update;
324
325 Ok(Self {
326 tool_call_id,
327 title: title.ok_or_else(|| {
328 Error::invalid_params().data(serde_json::json!("title is required for a tool call"))
329 })?,
330 kind: kind.unwrap_or_default(),
331 status: status.unwrap_or_default(),
332 content: content.unwrap_or_default(),
333 locations: locations.unwrap_or_default(),
334 raw_input,
335 raw_output,
336 meta,
337 })
338 }
339}
340
341impl From<ToolCall> for ToolCallUpdate {
342 fn from(value: ToolCall) -> Self {
343 let ToolCall {
344 tool_call_id,
345 title,
346 kind,
347 status,
348 content,
349 locations,
350 raw_input,
351 raw_output,
352 meta,
353 } = value;
354 Self {
355 tool_call_id,
356 fields: ToolCallUpdateFields {
357 kind: Some(kind),
358 status: Some(status),
359 title: Some(title),
360 content: Some(content),
361 locations: Some(locations),
362 raw_input,
363 raw_output,
364 },
365 meta,
366 }
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
372#[serde(transparent)]
373#[from(Arc<str>, String, &'static str)]
374#[non_exhaustive]
375pub struct ToolCallId(pub Arc<str>);
376
377impl ToolCallId {
378 #[must_use]
379 pub fn new(id: impl Into<Arc<str>>) -> Self {
380 Self(id.into())
381 }
382}
383
384impl IntoOption<ToolCallId> for &str {
385 fn into_option(self) -> Option<ToolCallId> {
386 Some(ToolCallId::new(self))
387 }
388}
389
390#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
397#[serde(rename_all = "snake_case")]
398#[non_exhaustive]
399pub enum ToolKind {
400 Read,
402 Edit,
404 Delete,
406 Move,
408 Search,
410 Execute,
412 Think,
414 Fetch,
416 SwitchMode,
418 #[default]
420 #[serde(other)]
421 Other,
422}
423
424impl ToolKind {
425 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
426 fn is_default(&self) -> bool {
427 matches!(self, ToolKind::Other)
428 }
429}
430
431#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
437#[serde(rename_all = "snake_case")]
438#[non_exhaustive]
439pub enum ToolCallStatus {
440 #[default]
443 Pending,
444 InProgress,
446 Completed,
448 Failed,
450}
451
452impl ToolCallStatus {
453 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
454 fn is_default(&self) -> bool {
455 matches!(self, ToolCallStatus::Pending)
456 }
457}
458
459#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
466#[serde(tag = "type", rename_all = "snake_case")]
467#[schemars(extend("discriminator" = {"propertyName": "type"}))]
468#[non_exhaustive]
469pub enum ToolCallContent {
470 Content(Content),
472 Diff(Diff),
474 Terminal(Terminal),
480}
481
482impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
483 fn from(content: T) -> Self {
484 ToolCallContent::Content(Content::new(content))
485 }
486}
487
488impl From<Diff> for ToolCallContent {
489 fn from(diff: Diff) -> Self {
490 ToolCallContent::Diff(diff)
491 }
492}
493
494#[skip_serializing_none]
496#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
497#[serde(rename_all = "camelCase")]
498#[non_exhaustive]
499pub struct Content {
500 pub content: ContentBlock,
502 #[serde(rename = "_meta")]
508 pub meta: Option<Meta>,
509}
510
511impl Content {
512 #[must_use]
513 pub fn new(content: impl Into<ContentBlock>) -> Self {
514 Self {
515 content: content.into(),
516 meta: None,
517 }
518 }
519
520 #[must_use]
526 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
527 self.meta = meta.into_option();
528 self
529 }
530}
531
532#[skip_serializing_none]
538#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
539#[serde(rename_all = "camelCase")]
540#[non_exhaustive]
541pub struct Terminal {
542 pub terminal_id: TerminalId,
543 #[serde(rename = "_meta")]
549 pub meta: Option<Meta>,
550}
551
552impl Terminal {
553 #[must_use]
554 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
555 Self {
556 terminal_id: terminal_id.into(),
557 meta: None,
558 }
559 }
560
561 #[must_use]
567 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
568 self.meta = meta.into_option();
569 self
570 }
571}
572
573#[skip_serializing_none]
579#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
580#[serde(rename_all = "camelCase")]
581#[non_exhaustive]
582pub struct Diff {
583 pub path: PathBuf,
585 pub old_text: Option<String>,
587 pub new_text: String,
589 #[serde(rename = "_meta")]
595 pub meta: Option<Meta>,
596}
597
598impl Diff {
599 #[must_use]
600 pub fn new(path: impl Into<PathBuf>, new_text: impl Into<String>) -> Self {
601 Self {
602 path: path.into(),
603 old_text: None,
604 new_text: new_text.into(),
605 meta: None,
606 }
607 }
608
609 #[must_use]
611 pub fn old_text(mut self, old_text: impl IntoOption<String>) -> Self {
612 self.old_text = old_text.into_option();
613 self
614 }
615
616 #[must_use]
622 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
623 self.meta = meta.into_option();
624 self
625 }
626}
627
628#[skip_serializing_none]
635#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
636#[serde(rename_all = "camelCase")]
637#[non_exhaustive]
638pub struct ToolCallLocation {
639 pub path: PathBuf,
641 #[serde(default)]
643 pub line: Option<u32>,
644 #[serde(rename = "_meta")]
650 pub meta: Option<Meta>,
651}
652
653impl ToolCallLocation {
654 #[must_use]
655 pub fn new(path: impl Into<PathBuf>) -> Self {
656 Self {
657 path: path.into(),
658 line: None,
659 meta: None,
660 }
661 }
662
663 #[must_use]
665 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
666 self.line = line.into_option();
667 self
668 }
669
670 #[must_use]
676 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
677 self.meta = meta.into_option();
678 self
679 }
680}