Skip to main content

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