agent_client_protocol/
tool_call.rs

1//! Tool calls represent actions that language models request agents to perform.
2//!
3//! When an LLM determines it needs to interact with external systems—like reading files,
4//! running code, or fetching data—it generates tool calls that the agent executes on its behalf.
5//!
6//! See: <https://agentclientprotocol.com/protocol/tool-calls>
7
8use std::{path::PathBuf, sync::Arc};
9
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use crate::{ContentBlock, Error};
14
15/// Represents a tool call that the language model has requested.
16///
17/// Tool calls are actions that the agent executes on behalf of the language model,
18/// such as reading files, executing code, or fetching data from external sources.
19///
20/// See: <https://agentclientprotocol.com/protocol/tool-calls>
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
22#[serde(rename_all = "camelCase")]
23pub struct ToolCall {
24    /// Unique identifier for this tool call within the session.
25    #[serde(rename = "toolCallId")]
26    pub id: ToolCallId,
27    /// Human-readable title describing what the tool is doing.
28    pub title: String,
29    /// The category of tool being invoked.
30    /// Helps clients choose appropriate icons and UI treatment.
31    #[serde(default, skip_serializing_if = "ToolKind::is_default")]
32    pub kind: ToolKind,
33    /// Current execution status of the tool call.
34    #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
35    pub status: ToolCallStatus,
36    /// Content produced by the tool call.
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    pub content: Vec<ToolCallContent>,
39    /// File locations affected by this tool call.
40    /// Enables "follow-along" features in clients.
41    #[serde(default, skip_serializing_if = "Vec::is_empty")]
42    pub locations: Vec<ToolCallLocation>,
43    /// Raw input parameters sent to the tool.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub raw_input: Option<serde_json::Value>,
46    /// Raw output returned by the tool.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub raw_output: Option<serde_json::Value>,
49}
50
51impl ToolCall {
52    /// Update an existing tool call with the values in the provided update
53    /// fields. Fields with collections of values are overwritten, not extended.
54    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/// An update to an existing tool call.
80///
81/// Used to report progress and results as tools execute. All fields except
82/// the tool call ID are optional - only changed fields need to be included.
83///
84/// See: <https://agentclientprotocol.com/protocol/tool-calls#updating>
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
86#[serde(rename_all = "camelCase")]
87pub struct ToolCallUpdate {
88    /// The ID of the tool call being updated.
89    #[serde(rename = "toolCallId")]
90    pub id: ToolCallId,
91    /// Fields being updated.
92    #[serde(flatten)]
93    pub fields: ToolCallUpdateFields,
94}
95
96/// Optional fields that can be updated in a tool call.
97///
98/// All fields are optional - only include the ones being changed.
99/// Collections (content, locations) are overwritten, not extended.
100#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
101#[serde(rename_all = "camelCase")]
102pub struct ToolCallUpdateFields {
103    /// Update the tool kind.
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub kind: Option<ToolKind>,
106    /// Update the execution status.
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub status: Option<ToolCallStatus>,
109    /// Update the human-readable title.
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub title: Option<String>,
112    /// Replace the content collection.
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub content: Option<Vec<ToolCallContent>>,
115    /// Replace the locations collection.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub locations: Option<Vec<ToolCallLocation>>,
118    /// Update the raw input.
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub raw_input: Option<serde_json::Value>,
121    /// Update the raw output.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub raw_output: Option<serde_json::Value>,
124}
125
126/// If a given tool call doesn't exist yet, allows for attempting to construct
127/// one from a tool call update if possible.
128impl 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/// Unique identifier for a tool call within a session.
190#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
191#[serde(transparent)]
192pub struct ToolCallId(pub Arc<str>);
193
194/// Categories of tools that can be invoked.
195///
196/// Tool kinds help clients choose appropriate icons and optimize how they
197/// display tool execution progress.
198///
199/// See: <https://agentclientprotocol.com/protocol/tool-calls#creating>
200#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
201#[serde(rename_all = "snake_case")]
202pub enum ToolKind {
203    /// Reading files or data.
204    Read,
205    /// Modifying files or content.
206    Edit,
207    /// Removing files or data.
208    Delete,
209    /// Moving or renaming files.
210    Move,
211    /// Searching for information.
212    Search,
213    /// Running commands or code.
214    Execute,
215    /// Internal reasoning or planning.
216    Think,
217    /// Retrieving external data.
218    Fetch,
219    /// Other tool types (default).
220    #[default]
221    Other,
222}
223
224impl ToolKind {
225    fn is_default(&self) -> bool {
226        matches!(self, ToolKind::Other)
227    }
228}
229
230/// Execution status of a tool call.
231///
232/// Tool calls progress through different statuses during their lifecycle.
233///
234/// See: <https://agentclientprotocol.com/protocol/tool-calls#status>
235#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
236#[serde(rename_all = "snake_case")]
237pub enum ToolCallStatus {
238    /// The tool call hasn't started running yet because the input is either
239    /// streaming or we're awaiting approval.
240    #[default]
241    Pending,
242    /// The tool call is currently running.
243    InProgress,
244    /// The tool call completed successfully.
245    Completed,
246    /// The tool call failed with an error.
247    Failed,
248}
249
250impl ToolCallStatus {
251    fn is_default(&self) -> bool {
252        matches!(self, ToolCallStatus::Pending)
253    }
254}
255
256/// Content produced by a tool call.
257///
258/// Tool calls can produce different types of content including
259/// standard content blocks (text, images) or file diffs.
260///
261/// See: <https://agentclientprotocol.com/protocol/tool-calls#content>
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
263#[serde(tag = "type", rename_all = "snake_case")]
264pub enum ToolCallContent {
265    /// Standard content block (text, images, resources).
266    Content {
267        /// The actual content block.
268        content: ContentBlock,
269    },
270    /// File modification shown as a diff.
271    Diff {
272        /// The diff details.
273        #[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/// A diff representing file modifications.
293///
294/// Shows changes to files in a format suitable for display in the client UI.
295///
296/// See: <https://agentclientprotocol.com/protocol/tool-calls#content>
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
298#[serde(rename_all = "camelCase")]
299pub struct Diff {
300    /// The file path being modified.
301    pub path: PathBuf,
302    /// The original content (None for new files).
303    pub old_text: Option<String>,
304    /// The new content after modification.
305    pub new_text: String,
306}
307
308/// A file location being accessed or modified by a tool.
309///
310/// Enables clients to implement "follow-along" features that track
311/// which files the agent is working with in real-time.
312///
313/// See: <https://agentclientprotocol.com/protocol/tool-calls#following-the-agent>
314#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
315#[serde(tag = "type", rename_all = "camelCase")]
316pub struct ToolCallLocation {
317    /// The file path being accessed or modified.
318    pub path: PathBuf,
319    /// Optional line number within the file.
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    pub line: Option<u32>,
322}