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]
469#[expect(clippy::large_enum_variant)]
470pub enum ToolCallContent {
471    /// Standard content block (text, images, resources).
472    Content(Content),
473    /// File modification shown as a diff.
474    Diff(Diff),
475    /// Embed a terminal created with `terminal/create` by its id.
476    ///
477    /// The terminal must be added before calling `terminal/release`.
478    ///
479    /// See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals)
480    Terminal(Terminal),
481}
482
483impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
484    fn from(content: T) -> Self {
485        ToolCallContent::Content(Content::new(content))
486    }
487}
488
489impl From<Diff> for ToolCallContent {
490    fn from(diff: Diff) -> Self {
491        ToolCallContent::Diff(diff)
492    }
493}
494
495/// Standard content block (text, images, resources).
496#[skip_serializing_none]
497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
498#[serde(rename_all = "camelCase")]
499#[non_exhaustive]
500pub struct Content {
501    /// The actual content block.
502    pub content: ContentBlock,
503    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
504    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
505    /// these keys.
506    ///
507    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
508    #[serde(rename = "_meta")]
509    pub meta: Option<Meta>,
510}
511
512impl Content {
513    #[must_use]
514    pub fn new(content: impl Into<ContentBlock>) -> Self {
515        Self {
516            content: content.into(),
517            meta: None,
518        }
519    }
520
521    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
522    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
523    /// these keys.
524    ///
525    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
526    #[must_use]
527    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
528        self.meta = meta.into_option();
529        self
530    }
531}
532
533/// Embed a terminal created with `terminal/create` by its id.
534///
535/// The terminal must be added before calling `terminal/release`.
536///
537/// See protocol docs: [Terminal](https://agentclientprotocol.com/protocol/terminals)
538#[skip_serializing_none]
539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
540#[serde(rename_all = "camelCase")]
541#[non_exhaustive]
542pub struct Terminal {
543    pub terminal_id: TerminalId,
544    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
545    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
546    /// these keys.
547    ///
548    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
549    #[serde(rename = "_meta")]
550    pub meta: Option<Meta>,
551}
552
553impl Terminal {
554    #[must_use]
555    pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
556        Self {
557            terminal_id: terminal_id.into(),
558            meta: None,
559        }
560    }
561
562    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
563    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
564    /// these keys.
565    ///
566    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
567    #[must_use]
568    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
569        self.meta = meta.into_option();
570        self
571    }
572}
573
574/// A diff representing file modifications.
575///
576/// Shows changes to files in a format suitable for display in the client UI.
577///
578/// See protocol docs: [Content](https://agentclientprotocol.com/protocol/tool-calls#content)
579#[skip_serializing_none]
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
581#[serde(rename_all = "camelCase")]
582#[non_exhaustive]
583pub struct Diff {
584    /// The file path being modified.
585    pub path: PathBuf,
586    /// The original content (None for new files).
587    pub old_text: Option<String>,
588    /// The new content after modification.
589    pub new_text: String,
590    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
591    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
592    /// these keys.
593    ///
594    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
595    #[serde(rename = "_meta")]
596    pub meta: Option<Meta>,
597}
598
599impl Diff {
600    #[must_use]
601    pub fn new(path: impl Into<PathBuf>, new_text: impl Into<String>) -> Self {
602        Self {
603            path: path.into(),
604            old_text: None,
605            new_text: new_text.into(),
606            meta: None,
607        }
608    }
609
610    /// The original content (None for new files).
611    #[must_use]
612    pub fn old_text(mut self, old_text: impl IntoOption<String>) -> Self {
613        self.old_text = old_text.into_option();
614        self
615    }
616
617    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
618    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
619    /// these keys.
620    ///
621    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
622    #[must_use]
623    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
624        self.meta = meta.into_option();
625        self
626    }
627}
628
629/// A file location being accessed or modified by a tool.
630///
631/// Enables clients to implement "follow-along" features that track
632/// which files the agent is working with in real-time.
633///
634/// See protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/tool-calls#following-the-agent)
635#[skip_serializing_none]
636#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
637#[serde(rename_all = "camelCase")]
638#[non_exhaustive]
639pub struct ToolCallLocation {
640    /// The file path being accessed or modified.
641    pub path: PathBuf,
642    /// Optional line number within the file.
643    #[serde(default)]
644    pub line: Option<u32>,
645    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
646    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
647    /// these keys.
648    ///
649    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
650    #[serde(rename = "_meta")]
651    pub meta: Option<Meta>,
652}
653
654impl ToolCallLocation {
655    #[must_use]
656    pub fn new(path: impl Into<PathBuf>) -> Self {
657        Self {
658            path: path.into(),
659            line: None,
660            meta: None,
661        }
662    }
663
664    /// Optional line number within the file.
665    #[must_use]
666    pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
667        self.line = line.into_option();
668        self
669    }
670
671    /// The _meta property is reserved by ACP to allow clients and agents to attach additional
672    /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
673    /// these keys.
674    ///
675    /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
676    #[must_use]
677    pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
678        self.meta = meta.into_option();
679        self
680    }
681}