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