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, TerminalId};
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#[non_exhaustive]
24pub struct ToolCall {
25    /// Unique identifier for this tool call within the session.
26    pub tool_call_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(skip_serializing_if = "Option::is_none")]
45    pub raw_input: Option<serde_json::Value>,
46    /// Raw output returned by the tool.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub raw_output: Option<serde_json::Value>,
49    /// Extension point for implementations
50    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
51    pub meta: Option<serde_json::Value>,
52}
53
54impl ToolCall {
55    pub fn new(tool_call_id: ToolCallId, title: impl Into<String>) -> Self {
56        Self {
57            tool_call_id,
58            title: title.into(),
59            kind: ToolKind::default(),
60            status: ToolCallStatus::default(),
61            content: Vec::default(),
62            locations: Vec::default(),
63            raw_input: None,
64            raw_output: None,
65            meta: None,
66        }
67    }
68
69    /// The category of tool being invoked.
70    /// Helps clients choose appropriate icons and UI treatment.
71    #[must_use]
72    pub fn kind(mut self, kind: ToolKind) -> Self {
73        self.kind = kind;
74        self
75    }
76
77    /// Current execution status of the tool call.
78    #[must_use]
79    pub fn status(mut self, status: ToolCallStatus) -> Self {
80        self.status = status;
81        self
82    }
83
84    /// Content produced by the tool call.
85    #[must_use]
86    pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
87        self.content = content;
88        self
89    }
90
91    /// File locations affected by this tool call.
92    /// Enables "follow-along" features in clients.
93    #[must_use]
94    pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
95        self.locations = locations;
96        self
97    }
98
99    /// Raw input parameters sent to the tool.
100    #[must_use]
101    pub fn raw_input(mut self, raw_input: serde_json::Value) -> Self {
102        self.raw_input = Some(raw_input);
103        self
104    }
105
106    /// Raw output returned by the tool.
107    #[must_use]
108    pub fn raw_output(mut self, raw_output: serde_json::Value) -> Self {
109        self.raw_output = Some(raw_output);
110        self
111    }
112
113    /// Extension point for implementations
114    #[must_use]
115    pub fn meta(mut self, meta: serde_json::Value) -> Self {
116        self.meta = Some(meta);
117        self
118    }
119
120    /// Update an existing tool call with the values in the provided update
121    /// fields. Fields with collections of values are overwritten, not extended.
122    pub fn update(&mut self, fields: ToolCallUpdateFields) {
123        if let Some(title) = fields.title {
124            self.title = title;
125        }
126        if let Some(kind) = fields.kind {
127            self.kind = kind;
128        }
129        if let Some(status) = fields.status {
130            self.status = status;
131        }
132        if let Some(content) = fields.content {
133            self.content = content;
134        }
135        if let Some(locations) = fields.locations {
136            self.locations = locations;
137        }
138        if let Some(raw_input) = fields.raw_input {
139            self.raw_input = Some(raw_input);
140        }
141        if let Some(raw_output) = fields.raw_output {
142            self.raw_output = Some(raw_output);
143        }
144    }
145}
146
147/// An update to an existing tool call.
148///
149/// Used to report progress and results as tools execute. All fields except
150/// the tool call ID are optional - only changed fields need to be included.
151///
152/// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating)
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
154#[serde(rename_all = "camelCase")]
155#[non_exhaustive]
156pub struct ToolCallUpdate {
157    /// The ID of the tool call being updated.
158    pub tool_call_id: ToolCallId,
159    /// Fields being updated.
160    #[serde(flatten)]
161    pub fields: ToolCallUpdateFields,
162    /// Extension point for implementations
163    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
164    pub meta: Option<serde_json::Value>,
165}
166
167impl ToolCallUpdate {
168    #[must_use]
169    pub fn new(tool_call_id: ToolCallId, fields: ToolCallUpdateFields) -> Self {
170        Self {
171            tool_call_id,
172            fields,
173            meta: None,
174        }
175    }
176
177    /// Extension point for implementations
178    #[must_use]
179    pub fn meta(mut self, meta: serde_json::Value) -> Self {
180        self.meta = Some(meta);
181        self
182    }
183}
184
185/// Optional fields that can be updated in a tool call.
186///
187/// All fields are optional - only include the ones being changed.
188/// Collections (content, locations) are overwritten, not extended.
189///
190/// See protocol docs: [Updating](https://agentclientprotocol.com/protocol/tool-calls#updating)
191#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
192#[serde(rename_all = "camelCase")]
193#[non_exhaustive]
194pub struct ToolCallUpdateFields {
195    /// Update the tool kind.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub kind: Option<ToolKind>,
198    /// Update the execution status.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub status: Option<ToolCallStatus>,
201    /// Update the human-readable title.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub title: Option<String>,
204    /// Replace the content collection.
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub content: Option<Vec<ToolCallContent>>,
207    /// Replace the locations collection.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub locations: Option<Vec<ToolCallLocation>>,
210    /// Update the raw input.
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub raw_input: Option<serde_json::Value>,
213    /// Update the raw output.
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub raw_output: Option<serde_json::Value>,
216}
217
218impl ToolCallUpdateFields {
219    #[must_use]
220    pub fn new() -> Self {
221        Self::default()
222    }
223
224    /// Update the tool kind.
225    #[must_use]
226    pub fn kind(mut self, kind: ToolKind) -> Self {
227        self.kind = Some(kind);
228        self
229    }
230
231    /// Update the execution status.
232    #[must_use]
233    pub fn status(mut self, status: ToolCallStatus) -> Self {
234        self.status = Some(status);
235        self
236    }
237
238    /// Update the human-readable title.
239    #[must_use]
240    pub fn title(mut self, title: impl Into<String>) -> Self {
241        self.title = Some(title.into());
242        self
243    }
244
245    /// Replace the content collection.
246    #[must_use]
247    pub fn content(mut self, content: Vec<ToolCallContent>) -> Self {
248        self.content = Some(content);
249        self
250    }
251
252    /// Replace the locations collection.
253    #[must_use]
254    pub fn locations(mut self, locations: Vec<ToolCallLocation>) -> Self {
255        self.locations = Some(locations);
256        self
257    }
258
259    /// Update the raw input.
260    #[must_use]
261    pub fn raw_input(mut self, raw_input: serde_json::Value) -> Self {
262        self.raw_input = Some(raw_input);
263        self
264    }
265
266    /// Update the raw output.
267    #[must_use]
268    pub fn raw_output(mut self, raw_output: serde_json::Value) -> Self {
269        self.raw_output = Some(raw_output);
270        self
271    }
272}
273
274/// If a given tool call doesn't exist yet, allows for attempting to construct
275/// one from a tool call update if possible.
276impl TryFrom<ToolCallUpdate> for ToolCall {
277    type Error = Error;
278
279    fn try_from(update: ToolCallUpdate) -> Result<Self, Self::Error> {
280        let ToolCallUpdate {
281            tool_call_id,
282            fields:
283                ToolCallUpdateFields {
284                    kind,
285                    status,
286                    title,
287                    content,
288                    locations,
289                    raw_input,
290                    raw_output,
291                },
292            meta,
293        } = update;
294
295        Ok(Self {
296            tool_call_id,
297            title: title.ok_or_else(|| {
298                Error::invalid_params().data(serde_json::json!("title is required for a tool call"))
299            })?,
300            kind: kind.unwrap_or_default(),
301            status: status.unwrap_or_default(),
302            content: content.unwrap_or_default(),
303            locations: locations.unwrap_or_default(),
304            raw_input,
305            raw_output,
306            meta,
307        })
308    }
309}
310
311impl From<ToolCall> for ToolCallUpdate {
312    fn from(value: ToolCall) -> Self {
313        let ToolCall {
314            tool_call_id,
315            title,
316            kind,
317            status,
318            content,
319            locations,
320            raw_input,
321            raw_output,
322            meta,
323        } = value;
324        Self {
325            tool_call_id,
326            fields: ToolCallUpdateFields {
327                kind: Some(kind),
328                status: Some(status),
329                title: Some(title),
330                content: Some(content),
331                locations: Some(locations),
332                raw_input,
333                raw_output,
334            },
335            meta,
336        }
337    }
338}
339
340/// Unique identifier for a tool call within a session.
341#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
342#[serde(transparent)]
343#[from(Arc<str>, String, &'static str)]
344#[non_exhaustive]
345pub struct ToolCallId(pub Arc<str>);
346
347impl ToolCallId {
348    pub fn new(id: impl Into<Arc<str>>) -> Self {
349        Self(id.into())
350    }
351}
352
353/// Categories of tools that can be invoked.
354///
355/// Tool kinds help clients choose appropriate icons and optimize how they
356/// display tool execution progress.
357///
358/// See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-calls#creating)
359#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
360#[serde(rename_all = "snake_case")]
361#[non_exhaustive]
362pub enum ToolKind {
363    /// Reading files or data.
364    Read,
365    /// Modifying files or content.
366    Edit,
367    /// Removing files or data.
368    Delete,
369    /// Moving or renaming files.
370    Move,
371    /// Searching for information.
372    Search,
373    /// Running commands or code.
374    Execute,
375    /// Internal reasoning or planning.
376    Think,
377    /// Retrieving external data.
378    Fetch,
379    /// Switching the current session mode.
380    SwitchMode,
381    /// Other tool types (default).
382    #[default]
383    #[serde(other)]
384    Other,
385}
386
387impl ToolKind {
388    #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
389    fn is_default(&self) -> bool {
390        matches!(self, ToolKind::Other)
391    }
392}
393
394/// Execution status of a tool call.
395///
396/// Tool calls progress through different statuses during their lifecycle.
397///
398/// See protocol docs: [Status](https://agentclientprotocol.com/protocol/tool-calls#status)
399#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
400#[serde(rename_all = "snake_case")]
401#[non_exhaustive]
402pub enum ToolCallStatus {
403    /// The tool call hasn't started running yet because the input is either
404    /// streaming or we're awaiting approval.
405    #[default]
406    Pending,
407    /// The tool call is currently running.
408    InProgress,
409    /// The tool call completed successfully.
410    Completed,
411    /// The tool call failed with an error.
412    Failed,
413}
414
415impl ToolCallStatus {
416    #[expect(clippy::trivially_copy_pass_by_ref, reason = "Required by serde")]
417    fn is_default(&self) -> bool {
418        matches!(self, ToolCallStatus::Pending)
419    }
420}
421
422/// Content produced by a tool call.
423///
424/// Tool calls can produce different types of content including
425/// standard content blocks (text, images) or file diffs.
426///
427/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
429#[serde(tag = "type", rename_all = "snake_case")]
430#[schemars(extend("discriminator" = {"propertyName": "type"}))]
431#[non_exhaustive]
432pub enum ToolCallContent {
433    /// Standard content block (text, images, resources).
434    Content(Content),
435    /// File modification shown as a diff.
436    Diff(Diff),
437    /// Embed a terminal created with `terminal/create` by its id.
438    ///
439    /// The terminal must be added before calling `terminal/release`.
440    ///
441    /// See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals)
442    Terminal(Terminal),
443}
444
445impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
446    fn from(content: T) -> Self {
447        ToolCallContent::Content(Content::new(content))
448    }
449}
450
451impl From<Diff> for ToolCallContent {
452    fn from(diff: Diff) -> Self {
453        ToolCallContent::Diff(diff)
454    }
455}
456
457/// Standard content block (text, images, resources).
458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
459#[serde(rename_all = "camelCase")]
460#[non_exhaustive]
461pub struct Content {
462    /// The actual content block.
463    pub content: ContentBlock,
464    /// Extension point for implementations
465    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
466    pub meta: Option<serde_json::Value>,
467}
468
469impl Content {
470    pub fn new(content: impl Into<ContentBlock>) -> Self {
471        Self {
472            content: content.into(),
473            meta: None,
474        }
475    }
476
477    /// Extension point for implementations
478    #[must_use]
479    pub fn meta(mut self, meta: serde_json::Value) -> Self {
480        self.meta = Some(meta);
481        self
482    }
483}
484
485/// Embed a terminal created with `terminal/create` by its id.
486///
487/// The terminal must be added before calling `terminal/release`.
488///
489/// See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals)
490#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
491#[serde(rename_all = "camelCase")]
492#[non_exhaustive]
493pub struct Terminal {
494    pub terminal_id: TerminalId,
495    /// Extension point for implementations
496    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
497    pub meta: Option<serde_json::Value>,
498}
499
500impl Terminal {
501    #[must_use]
502    pub fn new(terminal_id: TerminalId) -> Self {
503        Self {
504            terminal_id,
505            meta: None,
506        }
507    }
508
509    /// Extension point for implementations
510    #[must_use]
511    pub fn meta(mut self, meta: serde_json::Value) -> Self {
512        self.meta = Some(meta);
513        self
514    }
515}
516
517/// A diff representing file modifications.
518///
519/// Shows changes to files in a format suitable for display in the client UI.
520///
521/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
522#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
523#[serde(rename_all = "camelCase")]
524#[non_exhaustive]
525pub struct Diff {
526    /// The file path being modified.
527    pub path: PathBuf,
528    /// The original content (None for new files).
529    pub old_text: Option<String>,
530    /// The new content after modification.
531    pub new_text: String,
532    /// Extension point for implementations
533    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
534    pub meta: Option<serde_json::Value>,
535}
536
537impl Diff {
538    pub fn new(path: impl Into<PathBuf>, new_text: impl Into<String>) -> Self {
539        Self {
540            path: path.into(),
541            old_text: None,
542            new_text: new_text.into(),
543            meta: None,
544        }
545    }
546
547    /// The original content (None for new files).
548    #[must_use]
549    pub fn old_text(mut self, old_text: impl Into<String>) -> Self {
550        self.old_text = Some(old_text.into());
551        self
552    }
553
554    /// Extension point for implementations
555    #[must_use]
556    pub fn meta(mut self, meta: serde_json::Value) -> Self {
557        self.meta = Some(meta);
558        self
559    }
560}
561
562/// A file location being accessed or modified by a tool.
563///
564/// Enables clients to implement "follow-along" features that track
565/// which files the agent is working with in real-time.
566///
567/// See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent)
568#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
569#[serde(rename_all = "camelCase")]
570#[non_exhaustive]
571pub struct ToolCallLocation {
572    /// The file path being accessed or modified.
573    pub path: PathBuf,
574    /// Optional line number within the file.
575    #[serde(default, skip_serializing_if = "Option::is_none")]
576    pub line: Option<u32>,
577    /// Extension point for implementations
578    #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
579    pub meta: Option<serde_json::Value>,
580}
581
582impl ToolCallLocation {
583    pub fn new(path: impl Into<PathBuf>) -> Self {
584        Self {
585            path: path.into(),
586            line: None,
587            meta: None,
588        }
589    }
590
591    /// Optional line number within the file.
592    #[must_use]
593    pub fn line(mut self, line: u32) -> Self {
594        self.line = Some(line);
595        self
596    }
597
598    /// Extension point for implementations
599    #[must_use]
600    pub fn meta(mut self, meta: serde_json::Value) -> Self {
601        self.meta = Some(meta);
602        self
603    }
604}