Skip to main content

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