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