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