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