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