Skip to main content

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