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