1use crate::{ContentBlock, Meta, TerminalId, ToolCallId};
4use serde::{Deserialize, Serialize};
5use std::{collections::BTreeMap, path::PathBuf};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct ToolCall {
11 pub tool_call_id: ToolCallId,
12 pub title: String,
13 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub name: Option<String>,
16 #[serde(default, skip_serializing_if = "ToolKind::is_default")]
17 pub kind: ToolKind,
18 #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
19 pub status: ToolCallStatus,
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
21 pub content: Vec<ToolCallContent>,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 pub locations: Vec<ToolCallLocation>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub raw_input: Option<serde_json::Value>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub raw_output: Option<serde_json::Value>,
28 #[serde(default, rename = "_meta", skip_serializing_if = "Option::is_none")]
29 pub meta: Option<Meta>,
30}
31
32impl ToolCall {
33 pub fn new(tool_call_id: impl Into<ToolCallId>, title: impl Into<String>) -> Self {
34 Self {
35 tool_call_id: tool_call_id.into(),
36 title: title.into(),
37 name: None,
38 kind: ToolKind::default(),
39 status: ToolCallStatus::default(),
40 content: Vec::new(),
41 locations: Vec::new(),
42 raw_input: None,
43 raw_output: None,
44 meta: None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct ToolCallUpdate {
53 pub tool_call_id: ToolCallId,
54 #[serde(flatten)]
55 pub fields: ToolCallUpdateFields,
56 #[serde(default, rename = "_meta", skip_serializing_if = "Option::is_none")]
57 pub meta: Option<Meta>,
58}
59
60#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct ToolCallUpdateFields {
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub kind: Option<ToolKind>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub status: Option<ToolCallStatus>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub title: Option<String>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub name: Option<String>,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub content: Option<Vec<ToolCallContent>>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub locations: Option<Vec<ToolCallLocation>>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub raw_input: Option<serde_json::Value>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub raw_output: Option<serde_json::Value>,
79}
80
81impl ToolCallUpdate {
82 pub fn apply(self, target: &mut ToolCall) {
84 let ToolCallUpdateFields {
85 kind,
86 status,
87 title,
88 name,
89 content,
90 locations,
91 raw_input,
92 raw_output,
93 } = self.fields;
94 if let Some(kind) = kind {
95 target.kind = kind;
96 }
97 if let Some(status) = status {
98 target.status = status;
99 }
100 if let Some(title) = title {
101 target.title = title;
102 }
103 if name.is_some() {
104 target.name = name;
105 }
106 if let Some(content) = content {
107 target.content = content;
108 }
109 if let Some(locations) = locations {
110 target.locations = locations;
111 }
112 if raw_input.is_some() {
113 target.raw_input = raw_input;
114 }
115 if raw_output.is_some() {
116 target.raw_output = raw_output;
117 }
118 }
119}
120
121impl From<ToolCall> for ToolCallUpdate {
122 fn from(call: ToolCall) -> Self {
123 Self {
124 tool_call_id: call.tool_call_id,
125 fields: ToolCallUpdateFields {
126 kind: Some(call.kind),
127 status: Some(call.status),
128 title: Some(call.title),
129 name: call.name,
130 content: Some(call.content),
131 locations: Some(call.locations),
132 raw_input: call.raw_input,
133 raw_output: call.raw_output,
134 },
135 meta: None,
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(rename_all = "snake_case")]
146pub enum ToolKind {
147 Read,
148 Edit,
149 Delete,
150 Move,
151 Search,
152 Execute,
153 Think,
154 Fetch,
155 SwitchMode,
156 #[default]
157 #[serde(other)]
158 Other,
159}
160
161impl ToolKind {
162 fn is_default(&self) -> bool {
163 matches!(self, Self::Other)
164 }
165}
166
167#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "snake_case")]
169pub enum ToolCallStatus {
170 #[default]
171 Pending,
172 InProgress,
173 Completed,
174 Failed,
175 #[serde(untagged)]
176 Other(String),
177}
178
179impl ToolCallStatus {
180 fn is_default(&self) -> bool {
181 matches!(self, Self::Pending)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186#[serde(tag = "type", rename_all = "snake_case")]
187pub enum ToolCallContent {
188 Content {
189 content: ContentBlock,
190 },
191 Diff(Diff),
192 #[serde(rename_all = "camelCase")]
194 Terminal {
195 terminal_id: TerminalId,
196 },
197 #[serde(untagged)]
198 Other(OtherToolCallContent),
199}
200
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202pub struct OtherToolCallContent {
203 #[serde(rename = "type")]
204 pub kind: String,
205 #[serde(flatten)]
206 pub fields: BTreeMap<String, serde_json::Value>,
207}
208
209impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
210 fn from(content: T) -> Self {
211 Self::Content {
212 content: content.into(),
213 }
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct Diff {
221 pub path: PathBuf,
222 pub new_text: String,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
225 pub old_text: Option<String>,
226 #[serde(default, rename = "_meta", skip_serializing_if = "Option::is_none")]
227 pub meta: Option<Meta>,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct ToolCallLocation {
234 pub path: PathBuf,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub line: Option<u32>,
237 #[serde(default, rename = "_meta", skip_serializing_if = "Option::is_none")]
238 pub meta: Option<Meta>,
239}