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