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