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