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 protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/tool-calls)
7use std::{path::PathBuf, sync::Arc};
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::{ContentBlock, Error};
13
14/// Represents a tool call that the language model has requested.
15///
16/// Tool calls are actions that the agent executes on behalf of the language model,
17/// such as reading files, executing code, or fetching data from external sources.
18///
19/// See protocol docs: [Tool Calls](https://agentclientprotocol.com/protocol/tool-calls)
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
21#[serde(rename_all = "camelCase")]
22pub struct ToolCall {
23    /// Unique identifier for this tool call within the session.
24    #[serde(rename = "toolCallId")]
25    pub id: ToolCallId,
26    /// Human-readable title describing what the tool is doing.
27    pub title: String,
28    /// The category of tool being invoked.
29    /// Helps clients choose appropriate icons and UI treatment.
30    #[serde(default, skip_serializing_if = "ToolKind::is_default")]
31    pub kind: ToolKind,
32    /// Current execution status of the tool call.
33    #[serde(default, skip_serializing_if = "ToolCallStatus::is_default")]
34    pub status: ToolCallStatus,
35    /// Content produced by the tool call.
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub content: Vec<ToolCallContent>,
38    /// File locations affected by this tool call.
39    /// Enables "follow-along" features in clients.
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    pub locations: Vec<ToolCallLocation>,
42    /// Raw input parameters sent to the tool.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub raw_input: Option<serde_json::Value>,
45    /// Raw output returned by the tool.
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub raw_output: Option<serde_json::Value>,
48}
49
50impl ToolCall {
51    /// Update an existing tool call with the values in the provided update
52    /// fields. Fields with collections of values are overwritten, not extended.
53    pub fn update(&mut self, fields: ToolCallUpdateFields) {
54        if let Some(title) = fields.title {
55            self.title = title;
56        }
57        if let Some(kind) = fields.kind {
58            self.kind = kind;
59        }
60        if let Some(status) = fields.status {
61            self.status = status;
62        }
63        if let Some(content) = fields.content {
64            self.content = content;
65        }
66        if let Some(locations) = fields.locations {
67            self.locations = locations;
68        }
69        if let Some(raw_input) = fields.raw_input {
70            self.raw_input = Some(raw_input);
71        }
72        if let Some(raw_output) = fields.raw_output {
73            self.raw_output = Some(raw_output);
74        }
75    }
76}
77
78/// An update to an existing tool call.
79///
80/// Used to report progress and results as tools execute. All fields except
81/// the tool call ID are optional - only changed fields need to be included.
82///
83/// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating)
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
85#[serde(rename_all = "camelCase")]
86pub struct ToolCallUpdate {
87    /// The ID of the tool call being updated.
88    #[serde(rename = "toolCallId")]
89    pub id: ToolCallId,
90    /// Fields being updated.
91    #[serde(flatten)]
92    pub fields: ToolCallUpdateFields,
93}
94
95/// Optional fields that can be updated in a tool call.
96///
97/// All fields are optional - only include the ones being changed.
98/// Collections (content, locations) are overwritten, not extended.
99///
100/// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating)
101#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
102#[serde(rename_all = "camelCase")]
103pub struct ToolCallUpdateFields {
104    /// Update the tool kind.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub kind: Option<ToolKind>,
107    /// Update the execution status.
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub status: Option<ToolCallStatus>,
110    /// Update the human-readable title.
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub title: Option<String>,
113    /// Replace the content collection.
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub content: Option<Vec<ToolCallContent>>,
116    /// Replace the locations collection.
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub locations: Option<Vec<ToolCallLocation>>,
119    /// Update the raw input.
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub raw_input: Option<serde_json::Value>,
122    /// Update the raw output.
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub raw_output: Option<serde_json::Value>,
125}
126
127/// If a given tool call doesn't exist yet, allows for attempting to construct
128/// one from a tool call update if possible.
129impl TryFrom<ToolCallUpdate> for ToolCall {
130    type Error = Error;
131
132    fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
133        let ToolCallUpdate {
134            id,
135            fields:
136                ToolCallUpdateFields {
137                    kind,
138                    status,
139                    title,
140                    content,
141                    locations,
142                    raw_input,
143                    raw_output,
144                },
145        } = update;
146
147        Ok(Self {
148            id,
149            title: title.ok_or_else(|| {
150                Error::invalid_params()
151                    .with_data(serde_json::json!("title is required for a tool call"))
152            })?,
153            kind: kind.unwrap_or_default(),
154            status: status.unwrap_or_default(),
155            content: content.unwrap_or_default(),
156            locations: locations.unwrap_or_default(),
157            raw_input,
158            raw_output,
159        })
160    }
161}
162
163impl From<ToolCall> for ToolCallUpdate {
164    fn from(value: ToolCall) -> Self {
165        let ToolCall {
166            id,
167            title,
168            kind,
169            status,
170            content,
171            locations,
172            raw_input,
173            raw_output,
174        } = value;
175        Self {
176            id,
177            fields: ToolCallUpdateFields {
178                kind: Some(kind),
179                status: Some(status),
180                title: Some(title),
181                content: Some(content),
182                locations: Some(locations),
183                raw_input,
184                raw_output,
185            },
186        }
187    }
188}
189
190/// Unique identifier for a tool call within a session.
191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
192#[serde(transparent)]
193pub struct ToolCallId(pub Arc<str>);
194
195/// Categories of tools that can be invoked.
196///
197/// Tool kinds help clients choose appropriate icons and optimize how they
198/// display tool execution progress.
199///
200/// See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-calls#creating)
201#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
202#[serde(rename_all = "snake_case")]
203pub enum ToolKind {
204    /// Reading files or data.
205    Read,
206    /// Modifying files or content.
207    Edit,
208    /// Removing files or data.
209    Delete,
210    /// Moving or renaming files.
211    Move,
212    /// Searching for information.
213    Search,
214    /// Running commands or code.
215    Execute,
216    /// Internal reasoning or planning.
217    Think,
218    /// Retrieving external data.
219    Fetch,
220    /// Other tool types (default).
221    #[default]
222    Other,
223}
224
225impl ToolKind {
226    fn is_default(&self) -> bool {
227        matches!(self, ToolKind::Other)
228    }
229}
230
231/// Execution status of a tool call.
232///
233/// Tool calls progress through different statuses during their lifecycle.
234///
235/// See protocol docs: [Status](https://agentclientprotocol.com/protocol/tool-calls#status)
236#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
237#[serde(rename_all = "snake_case")]
238pub enum ToolCallStatus {
239    /// The tool call hasn't started running yet because the input is either
240    /// streaming or we're awaiting approval.
241    #[default]
242    Pending,
243    /// The tool call is currently running.
244    InProgress,
245    /// The tool call completed successfully.
246    Completed,
247    /// The tool call failed with an error.
248    Failed,
249}
250
251impl ToolCallStatus {
252    fn is_default(&self) -> bool {
253        matches!(self, ToolCallStatus::Pending)
254    }
255}
256
257/// Content produced by a tool call.
258///
259/// Tool calls can produce different types of content including
260/// standard content blocks (text, images) or file diffs.
261///
262/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
264#[serde(tag = "type", rename_all = "snake_case")]
265pub enum ToolCallContent {
266    /// Standard content block (text, images, resources).
267    Content {
268        /// The actual content block.
269        content: ContentBlock,
270    },
271    /// File modification shown as a diff.
272    Diff {
273        /// The diff details.
274        #[serde(flatten)]
275        diff: Diff,
276    },
277}
278
279impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
280    fn from(content: T) -> Self {
281        ToolCallContent::Content {
282            content: content.into(),
283        }
284    }
285}
286
287impl From<Diff> for ToolCallContent {
288    fn from(diff: Diff) -> Self {
289        ToolCallContent::Diff { diff }
290    }
291}
292
293/// A diff representing file modifications.
294///
295/// Shows changes to files in a format suitable for display in the client UI.
296///
297/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
299#[serde(rename_all = "camelCase")]
300pub struct Diff {
301    /// The file path being modified.
302    pub path: PathBuf,
303    /// The original content (None for new files).
304    pub old_text: Option<String>,
305    /// The new content after modification.
306    pub new_text: String,
307}
308
309/// A file location being accessed or modified by a tool.
310///
311/// Enables clients to implement "follow-along" features that track
312/// which files the agent is working with in real-time.
313///
314/// See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent)
315#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
316#[serde(tag = "type", rename_all = "camelCase")]
317pub struct ToolCallLocation {
318    /// The file path being accessed or modified.
319    pub path: PathBuf,
320    /// Optional line number within the file.
321    #[serde(default, skip_serializing_if = "Option::is_none")]
322    pub line: Option<u32>,
323}