1use std::{path::PathBuf, sync::Arc};
8
9use derive_more::{Display, From};
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use crate::{ContentBlock, Error, TerminalId};
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
22#[serde(rename_all = "camelCase")]
23#[non_exhaustive]
24pub struct ToolCall {
25 pub tool_call_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(skip_serializing_if = "Option::is_none")]
45 pub raw_input: Option<serde_json::Value>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub raw_output: Option<serde_json::Value>,
49 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
51 pub meta: Option<serde_json::Value>,
52}
53
54impl ToolCall {
55 pub fn new(tool_call_id: ToolCallId, title: impl Into<String>) -> Self {
56 Self {
57 tool_call_id,
58 title: title.into(),
59 kind: ToolKind::default(),
60 status: ToolCallStatus::default(),
61 content: Vec::default(),
62 locations: Vec::default(),
63 raw_input: None,
64 raw_output: None,
65 meta: None,
66 }
67 }
68
69 #[must_use]
72 pub fn kind(mut self, kind: ToolKind) -> Self {
73 self.kind = kind;
74 self
75 }
76
77 #[must_use]
79 pub fn status(mut self, status: ToolCallStatus) -> Self {
80 self.status = status;
81 self
82 }
83
84 #[must_use]
86 pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
87 self.content = content;
88 self
89 }
90
91 #[must_use]
94 pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
95 self.locations = locations;
96 self
97 }
98
99 #[must_use]
101 pub fn raw_input(mut self, raw_input: serde_json::Value) -> Self {
102 self.raw_input = Some(raw_input);
103 self
104 }
105
106 #[must_use]
108 pub fn raw_output(mut self, raw_output: serde_json::Value) -> Self {
109 self.raw_output = Some(raw_output);
110 self
111 }
112
113 #[must_use]
115 pub fn meta(mut self, meta: serde_json::Value) -> Self {
116 self.meta = Some(meta);
117 self
118 }
119
120 pub fn update(&mut self, fields: ToolCallUpdateFields) {
123 if let Some(title) = fields.title {
124 self.title = title;
125 }
126 if let Some(kind) = fields.kind {
127 self.kind = kind;
128 }
129 if let Some(status) = fields.status {
130 self.status = status;
131 }
132 if let Some(content) = fields.content {
133 self.content = content;
134 }
135 if let Some(locations) = fields.locations {
136 self.locations = locations;
137 }
138 if let Some(raw_input) = fields.raw_input {
139 self.raw_input = Some(raw_input);
140 }
141 if let Some(raw_output) = fields.raw_output {
142 self.raw_output = Some(raw_output);
143 }
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
154#[serde(rename_all = "camelCase")]
155#[non_exhaustive]
156pub struct ToolCallUpdate {
157 pub tool_call_id: ToolCallId,
159 #[serde(flatten)]
161 pub fields: ToolCallUpdateFields,
162 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
164 pub meta: Option<serde_json::Value>,
165}
166
167impl ToolCallUpdate {
168 #[must_use]
169 pub fn new(tool_call_id: ToolCallId, fields: ToolCallUpdateFields) -> Self {
170 Self {
171 tool_call_id,
172 fields,
173 meta: None,
174 }
175 }
176
177 #[must_use]
179 pub fn meta(mut self, meta: serde_json::Value) -> Self {
180 self.meta = Some(meta);
181 self
182 }
183}
184
185#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
192#[serde(rename_all = "camelCase")]
193#[non_exhaustive]
194pub struct ToolCallUpdateFields {
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub kind: Option<ToolKind>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub status: Option<ToolCallStatus>,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub title: Option<String>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub content: Option<Vec<ToolCallContent>>,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub locations: Option<Vec<ToolCallLocation>>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub raw_input: Option<serde_json::Value>,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub raw_output: Option<serde_json::Value>,
216}
217
218impl ToolCallUpdateFields {
219 #[must_use]
220 pub fn new() -> Self {
221 Self::default()
222 }
223
224 #[must_use]
226 pub fn kind(mut self, kind: ToolKind) -> Self {
227 self.kind = Some(kind);
228 self
229 }
230
231 #[must_use]
233 pub fn status(mut self, status: ToolCallStatus) -> Self {
234 self.status = Some(status);
235 self
236 }
237
238 #[must_use]
240 pub fn title(mut self, title: impl Into<String>) -> Self {
241 self.title = Some(title.into());
242 self
243 }
244
245 #[must_use]
247 pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
248 self.content = Some(content);
249 self
250 }
251
252 #[must_use]
254 pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
255 self.locations = Some(locations);
256 self
257 }
258
259 #[must_use]
261 pub fn raw_input(mut self, raw_input: serde_json::Value) -> Self {
262 self.raw_input = Some(raw_input);
263 self
264 }
265
266 #[must_use]
268 pub fn raw_output(mut self, raw_output: serde_json::Value) -> Self {
269 self.raw_output = Some(raw_output);
270 self
271 }
272}
273
274impl TryFrom<ToolCallUpdate> for ToolCall {
277 type Error = Error;
278
279 fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
280 let ToolCallUpdate {
281 tool_call_id,
282 fields:
283 ToolCallUpdateFields {
284 kind,
285 status,
286 title,
287 content,
288 locations,
289 raw_input,
290 raw_output,
291 },
292 meta,
293 } = update;
294
295 Ok(Self {
296 tool_call_id,
297 title: title.ok_or_else(|| {
298 Error::invalid_params().data(serde_json::json!("title is required for a tool call"))
299 })?,
300 kind: kind.unwrap_or_default(),
301 status: status.unwrap_or_default(),
302 content: content.unwrap_or_default(),
303 locations: locations.unwrap_or_default(),
304 raw_input,
305 raw_output,
306 meta,
307 })
308 }
309}
310
311impl From<ToolCall> for ToolCallUpdate {
312 fn from(value: ToolCall) -> Self {
313 let ToolCall {
314 tool_call_id,
315 title,
316 kind,
317 status,
318 content,
319 locations,
320 raw_input,
321 raw_output,
322 meta,
323 } = value;
324 Self {
325 tool_call_id,
326 fields: ToolCallUpdateFields {
327 kind: Some(kind),
328 status: Some(status),
329 title: Some(title),
330 content: Some(content),
331 locations: Some(locations),
332 raw_input,
333 raw_output,
334 },
335 meta,
336 }
337 }
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
342#[serde(transparent)]
343#[from(Arc<str>, String, &'static str)]
344#[non_exhaustive]
345pub struct ToolCallId(pub Arc<str>);
346
347impl ToolCallId {
348 pub fn new(id: impl Into<Arc<str>>) -> Self {
349 Self(id.into())
350 }
351}
352
353#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
360#[serde(rename_all = "snake_case")]
361#[non_exhaustive]
362pub enum ToolKind {
363 Read,
365 Edit,
367 Delete,
369 Move,
371 Search,
373 Execute,
375 Think,
377 Fetch,
379 SwitchMode,
381 #[default]
383 #[serde(other)]
384 Other,
385}
386
387impl ToolKind {
388 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
389 fn is_default(&self) -> bool {
390 matches!(self, ToolKind::Other)
391 }
392}
393
394#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
400#[serde(rename_all = "snake_case")]
401#[non_exhaustive]
402pub enum ToolCallStatus {
403 #[default]
406 Pending,
407 InProgress,
409 Completed,
411 Failed,
413}
414
415impl ToolCallStatus {
416 #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
417 fn is_default(&self) -> bool {
418 matches!(self, ToolCallStatus::Pending)
419 }
420}
421
422#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
429#[serde(tag = "type", rename_all = "snake_case")]
430#[schemars(extend("discriminator" = {"propertyName": "type"}))]
431#[non_exhaustive]
432pub enum ToolCallContent {
433 Content(Content),
435 Diff(Diff),
437 Terminal(Terminal),
443}
444
445impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
446 fn from(content: T) -> Self {
447 ToolCallContent::Content(Content::new(content))
448 }
449}
450
451impl From<Diff> for ToolCallContent {
452 fn from(diff: Diff) -> Self {
453 ToolCallContent::Diff(diff)
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
459#[serde(rename_all = "camelCase")]
460#[non_exhaustive]
461pub struct Content {
462 pub content: ContentBlock,
464 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
466 pub meta: Option<serde_json::Value>,
467}
468
469impl Content {
470 pub fn new(content: impl Into<ContentBlock>) -> Self {
471 Self {
472 content: content.into(),
473 meta: None,
474 }
475 }
476
477 #[must_use]
479 pub fn meta(mut self, meta: serde_json::Value) -> Self {
480 self.meta = Some(meta);
481 self
482 }
483}
484
485#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
491#[serde(rename_all = "camelCase")]
492#[non_exhaustive]
493pub struct Terminal {
494 pub terminal_id: TerminalId,
495 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
497 pub meta: Option<serde_json::Value>,
498}
499
500impl Terminal {
501 #[must_use]
502 pub fn new(terminal_id: TerminalId) -> Self {
503 Self {
504 terminal_id,
505 meta: None,
506 }
507 }
508
509 #[must_use]
511 pub fn meta(mut self, meta: serde_json::Value) -> Self {
512 self.meta = Some(meta);
513 self
514 }
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
523#[serde(rename_all = "camelCase")]
524#[non_exhaustive]
525pub struct Diff {
526 pub path: PathBuf,
528 pub old_text: Option<String>,
530 pub new_text: String,
532 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
534 pub meta: Option<serde_json::Value>,
535}
536
537impl Diff {
538 pub fn new(path: impl Into<PathBuf>, new_text: impl Into<String>) -> Self {
539 Self {
540 path: path.into(),
541 old_text: None,
542 new_text: new_text.into(),
543 meta: None,
544 }
545 }
546
547 #[must_use]
549 pub fn old_text(mut self, old_text: impl Into<String>) -> Self {
550 self.old_text = Some(old_text.into());
551 self
552 }
553
554 #[must_use]
556 pub fn meta(mut self, meta: serde_json::Value) -> Self {
557 self.meta = Some(meta);
558 self
559 }
560}
561
562#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
569#[serde(rename_all = "camelCase")]
570#[non_exhaustive]
571pub struct ToolCallLocation {
572 pub path: PathBuf,
574 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub line: Option<u32>,
577 #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
579 pub meta: Option<serde_json::Value>,
580}
581
582impl ToolCallLocation {
583 pub fn new(path: impl Into<PathBuf>) -> Self {
584 Self {
585 path: path.into(),
586 line: None,
587 meta: None,
588 }
589 }
590
591 #[must_use]
593 pub fn line(mut self, line: u32) -> Self {
594 self.line = Some(line);
595 self
596 }
597
598 #[must_use]
600 pub fn meta(mut self, meta: serde_json::Value) -> Self {
601 self.meta = Some(meta);
602 self
603 }
604}