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::{IntoOption, SkipListener};
15
16use super::{ContentBlock, Error, Meta, TerminalId};
17
18#[serde_as]
25#[skip_serializing_none]
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
27#[serde(rename_all = "camelCase")]
28#[non_exhaustive]
29pub struct ToolCall {
30 pub tool_call_id: ToolCallId,
32 pub title: String,
34 #[serde_as(deserialize_as = "DefaultOnError")]
37 #[schemars(extend("x-deserialize-default-on-error" = true))]
38 #[serde(default, skip_serializing_if = "ToolKind::is_default")]
39 pub kind: ToolKind,
40 #[serde_as(deserialize_as = "DefaultOnError")]
42 #[schemars(extend("x-deserialize-default-on-error" = true))]
43 #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
44 pub status: ToolCallStatus,
45 #[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 content: Vec<ToolCallContent>,
50 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
53 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
54 #[serde(default, skip_serializing_if = "Vec::is_empty")]
55 pub locations: Vec<ToolCallLocation>,
56 #[serde_as(deserialize_as = "DefaultOnError")]
58 #[schemars(extend("x-deserialize-default-on-error" = true))]
59 #[serde(default)]
60 pub raw_input: Option<serde_json::Value>,
61 #[serde_as(deserialize_as = "DefaultOnError")]
63 #[schemars(extend("x-deserialize-default-on-error" = true))]
64 #[serde(default)]
65 pub raw_output: Option<serde_json::Value>,
66 #[serde_as(deserialize_as = "DefaultOnError")]
72 #[schemars(extend("x-deserialize-default-on-error" = true))]
73 #[serde(default)]
74 #[serde(rename = "_meta")]
75 pub meta: Option<Meta>,
76}
77
78impl ToolCall {
79 #[must_use]
81 pub fn new(tool_call_id: impl Into<ToolCallId>, title: impl Into<String>) -> Self {
82 Self {
83 tool_call_id: tool_call_id.into(),
84 title: title.into(),
85 kind: ToolKind::default(),
86 status: ToolCallStatus::default(),
87 content: Vec::default(),
88 locations: Vec::default(),
89 raw_input: None,
90 raw_output: None,
91 meta: None,
92 }
93 }
94
95 #[must_use]
98 pub fn kind(mut self, kind: ToolKind) -> Self {
99 self.kind = kind;
100 self
101 }
102
103 #[must_use]
105 pub fn status(mut self, status: ToolCallStatus) -> Self {
106 self.status = status;
107 self
108 }
109
110 #[must_use]
112 pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
113 self.content = content;
114 self
115 }
116
117 #[must_use]
120 pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
121 self.locations = locations;
122 self
123 }
124
125 #[must_use]
127 pub fn raw_input(mut self, raw_input: impl IntoOption<serde_json::Value>) -> Self {
128 self.raw_input = raw_input.into_option();
129 self
130 }
131
132 #[must_use]
134 pub fn raw_output(mut self, raw_output: impl IntoOption<serde_json::Value>) -> Self {
135 self.raw_output = raw_output.into_option();
136 self
137 }
138
139 #[must_use]
145 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
146 self.meta = meta.into_option();
147 self
148 }
149
150 pub fn update(&mut self, fields: ToolCallUpdateFields) {
153 if let Some(title) = fields.title {
154 self.title = title;
155 }
156 if let Some(kind) = fields.kind {
157 self.kind = kind;
158 }
159 if let Some(status) = fields.status {
160 self.status = status;
161 }
162 if let Some(content) = fields.content {
163 self.content = content;
164 }
165 if let Some(locations) = fields.locations {
166 self.locations = locations;
167 }
168 if let Some(raw_input) = fields.raw_input {
169 self.raw_input = Some(raw_input);
170 }
171 if let Some(raw_output) = fields.raw_output {
172 self.raw_output = Some(raw_output);
173 }
174 }
175}
176
177#[serde_as]
184#[skip_serializing_none]
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
186#[serde(rename_all = "camelCase")]
187#[non_exhaustive]
188pub struct ToolCallUpdate {
189 pub tool_call_id: ToolCallId,
191 #[serde(flatten)]
193 pub fields: ToolCallUpdateFields,
194 #[serde_as(deserialize_as = "DefaultOnError")]
200 #[schemars(extend("x-deserialize-default-on-error" = true))]
201 #[serde(default)]
202 #[serde(rename = "_meta")]
203 pub meta: Option<Meta>,
204}
205
206impl ToolCallUpdate {
207 #[must_use]
209 pub fn new(tool_call_id: impl Into<ToolCallId>, fields: ToolCallUpdateFields) -> Self {
210 Self {
211 tool_call_id: tool_call_id.into(),
212 fields,
213 meta: None,
214 }
215 }
216
217 #[must_use]
223 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
224 self.meta = meta.into_option();
225 self
226 }
227}
228
229#[serde_as]
236#[skip_serializing_none]
237#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
238#[serde(rename_all = "camelCase")]
239#[non_exhaustive]
240pub struct ToolCallUpdateFields {
241 #[serde_as(deserialize_as = "DefaultOnError")]
243 #[schemars(extend("x-deserialize-default-on-error" = true))]
244 #[serde(default)]
245 pub kind: Option<ToolKind>,
246 #[serde_as(deserialize_as = "DefaultOnError")]
248 #[schemars(extend("x-deserialize-default-on-error" = true))]
249 #[serde(default)]
250 pub status: Option<ToolCallStatus>,
251 #[serde_as(deserialize_as = "DefaultOnError")]
253 #[schemars(extend("x-deserialize-default-on-error" = true))]
254 #[serde(default)]
255 pub title: Option<String>,
256 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
258 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
259 #[serde(default)]
260 pub content: Option<Vec<ToolCallContent>>,
261 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
263 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
264 #[serde(default)]
265 pub locations: Option<Vec<ToolCallLocation>>,
266 #[serde_as(deserialize_as = "DefaultOnError")]
268 #[schemars(extend("x-deserialize-default-on-error" = true))]
269 #[serde(default)]
270 pub raw_input: Option<serde_json::Value>,
271 #[serde_as(deserialize_as = "DefaultOnError")]
273 #[schemars(extend("x-deserialize-default-on-error" = true))]
274 #[serde(default)]
275 pub raw_output: Option<serde_json::Value>,
276}
277
278impl ToolCallUpdateFields {
279 #[must_use]
281 pub fn new() -> Self {
282 Self::default()
283 }
284
285 #[must_use]
287 pub fn kind(mut self, kind: impl IntoOption<ToolKind>) -> Self {
288 self.kind = kind.into_option();
289 self
290 }
291
292 #[must_use]
294 pub fn status(mut self, status: impl IntoOption<ToolCallStatus>) -> Self {
295 self.status = status.into_option();
296 self
297 }
298
299 #[must_use]
301 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
302 self.title = title.into_option();
303 self
304 }
305
306 #[must_use]
308 pub fn content(mut self, content: impl IntoOption<Vec<ToolCallContent>>) -> Self {
309 self.content = content.into_option();
310 self
311 }
312
313 #[must_use]
315 pub fn locations(mut self, locations: impl IntoOption<Vec<ToolCallLocation>>) -> Self {
316 self.locations = locations.into_option();
317 self
318 }
319
320 #[must_use]
322 pub fn raw_input(mut self, raw_input: impl IntoOption<serde_json::Value>) -> Self {
323 self.raw_input = raw_input.into_option();
324 self
325 }
326
327 #[must_use]
329 pub fn raw_output(mut self, raw_output: impl IntoOption<serde_json::Value>) -> Self {
330 self.raw_output = raw_output.into_option();
331 self
332 }
333}
334
335impl TryFrom<ToolCallUpdate> for ToolCall {
338 type Error = Error;
339
340 fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
341 let ToolCallUpdate {
342 tool_call_id,
343 fields:
344 ToolCallUpdateFields {
345 kind,
346 status,
347 title,
348 content,
349 locations,
350 raw_input,
351 raw_output,
352 },
353 meta,
354 } = update;
355
356 Ok(Self {
357 tool_call_id,
358 title: title.ok_or_else(|| {
359 Error::invalid_params().data(serde_json::json!("title is required for a tool call"))
360 })?,
361 kind: kind.unwrap_or_default(),
362 status: status.unwrap_or_default(),
363 content: content.unwrap_or_default(),
364 locations: locations.unwrap_or_default(),
365 raw_input,
366 raw_output,
367 meta,
368 })
369 }
370}
371
372impl From<ToolCall> for ToolCallUpdate {
373 fn from(value: ToolCall) -> Self {
374 let ToolCall {
375 tool_call_id,
376 title,
377 kind,
378 status,
379 content,
380 locations,
381 raw_input,
382 raw_output,
383 meta,
384 } = value;
385 Self {
386 tool_call_id,
387 fields: ToolCallUpdateFields {
388 kind: Some(kind),
389 status: Some(status),
390 title: Some(title),
391 content: Some(content),
392 locations: Some(locations),
393 raw_input,
394 raw_output,
395 },
396 meta,
397 }
398 }
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
403#[serde(transparent)]
404#[from(Arc<str>, String, &'static str)]
405#[non_exhaustive]
406pub struct ToolCallId(pub Arc<str>);
407
408impl ToolCallId {
409 #[must_use]
411 pub fn new(id: impl Into<Arc<str>>) -> Self {
412 Self(id.into())
413 }
414}
415
416impl IntoOption<ToolCallId> for &str {
417 fn into_option(self) -> Option<ToolCallId> {
418 Some(ToolCallId::new(self))
419 }
420}
421
422#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
429#[serde(rename_all = "snake_case")]
430#[non_exhaustive]
431pub enum ToolKind {
432 Read,
434 Edit,
436 Delete,
438 Move,
440 Search,
442 Execute,
444 Think,
446 Fetch,
448 SwitchMode,
450 #[default]
452 #[serde(other)]
453 Other,
454}
455
456impl ToolKind {
457 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
458 fn is_default(&self) -> bool {
459 matches!(self, ToolKind::Other)
460 }
461}
462
463#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
469#[serde(rename_all = "snake_case")]
470#[non_exhaustive]
471pub enum ToolCallStatus {
472 #[default]
475 Pending,
476 InProgress,
478 Completed,
480 Failed,
482}
483
484impl ToolCallStatus {
485 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
486 fn is_default(&self) -> bool {
487 matches!(self, ToolCallStatus::Pending)
488 }
489}
490
491#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
498#[serde(tag = "type", rename_all = "snake_case")]
499#[schemars(extend("discriminator" = {"propertyName": "type"}))]
500#[non_exhaustive]
501#[expect(clippy::large_enum_variant)]
502pub enum ToolCallContent {
503 Content(Content),
505 Diff(Diff),
507 Terminal(Terminal),
513}
514
515impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
516 fn from(content: T) -> Self {
517 ToolCallContent::Content(Content::new(content))
518 }
519}
520
521impl From<Diff> for ToolCallContent {
522 fn from(diff: Diff) -> Self {
523 ToolCallContent::Diff(diff)
524 }
525}
526
527#[serde_as]
529#[skip_serializing_none]
530#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
531#[serde(rename_all = "camelCase")]
532#[non_exhaustive]
533pub struct Content {
534 pub content: ContentBlock,
536 #[serde_as(deserialize_as = "DefaultOnError")]
542 #[schemars(extend("x-deserialize-default-on-error" = true))]
543 #[serde(default)]
544 #[serde(rename = "_meta")]
545 pub meta: Option<Meta>,
546}
547
548impl Content {
549 #[must_use]
551 pub fn new(content: impl Into<ContentBlock>) -> Self {
552 Self {
553 content: content.into(),
554 meta: None,
555 }
556 }
557
558 #[must_use]
564 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
565 self.meta = meta.into_option();
566 self
567 }
568}
569
570#[serde_as]
576#[skip_serializing_none]
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
578#[serde(rename_all = "camelCase")]
579#[non_exhaustive]
580pub struct Terminal {
581 pub terminal_id: TerminalId,
583 #[serde_as(deserialize_as = "DefaultOnError")]
589 #[schemars(extend("x-deserialize-default-on-error" = true))]
590 #[serde(default)]
591 #[serde(rename = "_meta")]
592 pub meta: Option<Meta>,
593}
594
595impl Terminal {
596 #[must_use]
598 pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
599 Self {
600 terminal_id: terminal_id.into(),
601 meta: None,
602 }
603 }
604
605 #[must_use]
611 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
612 self.meta = meta.into_option();
613 self
614 }
615}
616
617#[serde_as]
623#[skip_serializing_none]
624#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
625#[serde(rename_all = "camelCase")]
626#[non_exhaustive]
627pub struct Diff {
628 pub path: PathBuf,
630 #[serde_as(deserialize_as = "DefaultOnError")]
632 #[schemars(extend("x-deserialize-default-on-error" = true))]
633 #[serde(default)]
634 pub old_text: Option<String>,
635 pub new_text: String,
637 #[serde_as(deserialize_as = "DefaultOnError")]
643 #[schemars(extend("x-deserialize-default-on-error" = true))]
644 #[serde(default)]
645 #[serde(rename = "_meta")]
646 pub meta: Option<Meta>,
647}
648
649impl Diff {
650 #[must_use]
652 pub fn new(path: impl Into<PathBuf>, new_text: impl Into<String>) -> Self {
653 Self {
654 path: path.into(),
655 old_text: None,
656 new_text: new_text.into(),
657 meta: None,
658 }
659 }
660
661 #[must_use]
663 pub fn old_text(mut self, old_text: impl IntoOption<String>) -> Self {
664 self.old_text = old_text.into_option();
665 self
666 }
667
668 #[must_use]
674 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
675 self.meta = meta.into_option();
676 self
677 }
678}
679
680#[serde_as]
687#[skip_serializing_none]
688#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
689#[serde(rename_all = "camelCase")]
690#[non_exhaustive]
691pub struct ToolCallLocation {
692 pub path: PathBuf,
694 #[serde_as(deserialize_as = "DefaultOnError")]
696 #[schemars(extend("x-deserialize-default-on-error" = true))]
697 #[serde(default)]
698 pub line: Option<u32>,
699 #[serde_as(deserialize_as = "DefaultOnError")]
705 #[schemars(extend("x-deserialize-default-on-error" = true))]
706 #[serde(default)]
707 #[serde(rename = "_meta")]
708 pub meta: Option<Meta>,
709}
710
711impl ToolCallLocation {
712 #[must_use]
714 pub fn new(path: impl Into<PathBuf>) -> Self {
715 Self {
716 path: path.into(),
717 line: None,
718 meta: None,
719 }
720 }
721
722 #[must_use]
724 pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
725 self.line = line.into_option();
726 self
727 }
728
729 #[must_use]
735 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
736 self.meta = meta.into_option();
737 self
738 }
739}