agent_client_protocol_schema/v1/plan.rs
1//! Execution plans for complex tasks that require multiple steps.
2//!
3//! Plans are strategies that agents share with clients through session updates,
4//! providing real-time visibility into their thinking and progress.
5//!
6//! See: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
7
8#[cfg(feature = "unstable_plan_operations")]
9use std::sync::Arc;
10
11#[cfg(feature = "unstable_plan_operations")]
12use derive_more::{Display, From};
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
16
17use crate::{IntoOption, SkipListener};
18
19use super::Meta;
20
21/// An execution plan for accomplishing complex tasks.
22///
23/// Plans consist of multiple entries representing individual tasks or goals.
24/// Agents report plans to clients to provide visibility into their execution strategy.
25/// Plans can evolve during execution as the agent discovers new requirements or completes tasks.
26///
27/// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
28#[serde_as]
29#[skip_serializing_none]
30#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32#[non_exhaustive]
33pub struct Plan {
34 /// The list of tasks to be accomplished.
35 ///
36 /// When updating a plan, the agent must send a complete list of all entries
37 /// with their current status. The client replaces the entire plan with each update.
38 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
39 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
40 pub entries: Vec<PlanEntry>,
41 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
42 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
43 /// these keys.
44 ///
45 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
46 #[serde_as(deserialize_as = "DefaultOnError")]
47 #[schemars(extend("x-deserialize-default-on-error" = true))]
48 #[serde(default)]
49 #[serde(rename = "_meta")]
50 pub meta: Option<Meta>,
51}
52
53impl Plan {
54 /// Builds [`Plan`] with the required fields set; optional fields start unset or empty.
55 #[must_use]
56 pub fn new(entries: Vec<PlanEntry>) -> Self {
57 Self {
58 entries,
59 meta: None,
60 }
61 }
62
63 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
64 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
65 /// these keys.
66 ///
67 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
68 #[must_use]
69 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
70 self.meta = meta.into_option();
71 self
72 }
73}
74
75/// **UNSTABLE**
76///
77/// This capability is not part of the spec yet, and may be removed or changed at any point.
78///
79/// Unique identifier for a plan within a session.
80#[cfg(feature = "unstable_plan_operations")]
81#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
82#[serde(transparent)]
83#[from(Arc<str>, String, &'static str)]
84#[non_exhaustive]
85pub struct PlanId(pub Arc<str>);
86
87#[cfg(feature = "unstable_plan_operations")]
88impl PlanId {
89 /// Wraps a protocol string as a typed [`PlanId`].
90 #[must_use]
91 pub fn new(id: impl Into<Arc<str>>) -> Self {
92 Self(id.into())
93 }
94}
95
96/// **UNSTABLE**
97///
98/// This capability is not part of the spec yet, and may be removed or changed at any point.
99///
100/// A content update for a plan identified by ID.
101#[cfg(feature = "unstable_plan_operations")]
102#[serde_as]
103#[skip_serializing_none]
104#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
105#[serde(rename_all = "camelCase")]
106#[non_exhaustive]
107pub struct PlanUpdate {
108 /// The updated plan content.
109 pub plan: PlanUpdateContent,
110 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
111 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
112 /// these keys.
113 ///
114 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
115 #[serde_as(deserialize_as = "DefaultOnError")]
116 #[schemars(extend("x-deserialize-default-on-error" = true))]
117 #[serde(default)]
118 #[serde(rename = "_meta")]
119 pub meta: Option<Meta>,
120}
121
122#[cfg(feature = "unstable_plan_operations")]
123impl PlanUpdate {
124 /// Builds [`PlanUpdate`] with the required fields set; optional fields start unset or empty.
125 #[must_use]
126 pub fn new(plan: PlanUpdateContent) -> Self {
127 Self { plan, meta: None }
128 }
129
130 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
131 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
132 /// these keys.
133 ///
134 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
135 #[must_use]
136 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
137 self.meta = meta.into_option();
138 self
139 }
140}
141
142/// **UNSTABLE**
143///
144/// This capability is not part of the spec yet, and may be removed or changed at any point.
145///
146/// Updated content for a plan.
147#[cfg(feature = "unstable_plan_operations")]
148#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
149#[serde(tag = "type", rename_all = "snake_case")]
150#[schemars(extend("discriminator" = {"propertyName": "type"}))]
151#[non_exhaustive]
152pub enum PlanUpdateContent {
153 /// Structured plan entries.
154 Items(PlanItems),
155 /// A URI pointing to a file containing the plan.
156 File(PlanFile),
157 /// Raw markdown content for the plan.
158 Markdown(PlanMarkdown),
159}
160
161#[cfg(feature = "unstable_plan_operations")]
162impl PlanUpdateContent {
163 /// Builds a plan update that replaces the itemized entries for a plan.
164 #[must_use]
165 pub fn items(plan_id: impl Into<PlanId>, entries: Vec<PlanEntry>) -> Self {
166 Self::Items(PlanItems::new(plan_id, entries))
167 }
168
169 /// Builds a plan update that points clients at an external plan file URI.
170 #[must_use]
171 pub fn file(plan_id: impl Into<PlanId>, uri: impl Into<String>) -> Self {
172 Self::File(PlanFile::new(plan_id, uri))
173 }
174
175 /// Builds a plan update whose plan content is inline Markdown.
176 #[must_use]
177 pub fn markdown(plan_id: impl Into<PlanId>, content: impl Into<String>) -> Self {
178 Self::Markdown(PlanMarkdown::new(plan_id, content))
179 }
180}
181
182/// **UNSTABLE**
183///
184/// This capability is not part of the spec yet, and may be removed or changed at any point.
185///
186/// A plan represented as structured entries.
187#[cfg(feature = "unstable_plan_operations")]
188#[serde_as]
189#[skip_serializing_none]
190#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
191#[serde(rename_all = "camelCase")]
192#[non_exhaustive]
193pub struct PlanItems {
194 /// The plan ID to update.
195 pub plan_id: PlanId,
196 /// The list of tasks to be accomplished.
197 ///
198 /// When updating an item-based plan, the agent must send a complete list of all entries
199 /// with their current status. The client replaces that plan with each update.
200 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
201 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
202 pub entries: Vec<PlanEntry>,
203 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
204 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
205 /// these keys.
206 ///
207 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
208 #[serde_as(deserialize_as = "DefaultOnError")]
209 #[schemars(extend("x-deserialize-default-on-error" = true))]
210 #[serde(default)]
211 #[serde(rename = "_meta")]
212 pub meta: Option<Meta>,
213}
214
215#[cfg(feature = "unstable_plan_operations")]
216impl PlanItems {
217 /// Builds [`PlanItems`] with the required fields set; optional fields start unset or empty.
218 #[must_use]
219 pub fn new(plan_id: impl Into<PlanId>, entries: Vec<PlanEntry>) -> Self {
220 Self {
221 plan_id: plan_id.into(),
222 entries,
223 meta: None,
224 }
225 }
226
227 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
228 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
229 /// these keys.
230 ///
231 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
232 #[must_use]
233 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
234 self.meta = meta.into_option();
235 self
236 }
237}
238
239/// **UNSTABLE**
240///
241/// This capability is not part of the spec yet, and may be removed or changed at any point.
242///
243/// A plan represented by a file URI.
244#[cfg(feature = "unstable_plan_operations")]
245#[serde_as]
246#[skip_serializing_none]
247#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
248#[serde(rename_all = "camelCase")]
249#[non_exhaustive]
250pub struct PlanFile {
251 /// The plan ID to update.
252 pub plan_id: PlanId,
253 /// The URI of the file containing the plan.
254 pub uri: String,
255 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
256 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
257 /// these keys.
258 ///
259 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
260 #[serde_as(deserialize_as = "DefaultOnError")]
261 #[schemars(extend("x-deserialize-default-on-error" = true))]
262 #[serde(default)]
263 #[serde(rename = "_meta")]
264 pub meta: Option<Meta>,
265}
266
267#[cfg(feature = "unstable_plan_operations")]
268impl PlanFile {
269 /// Builds [`PlanFile`] with the required fields set; optional fields start unset or empty.
270 #[must_use]
271 pub fn new(plan_id: impl Into<PlanId>, uri: impl Into<String>) -> Self {
272 Self {
273 plan_id: plan_id.into(),
274 uri: uri.into(),
275 meta: None,
276 }
277 }
278
279 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
280 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
281 /// these keys.
282 ///
283 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
284 #[must_use]
285 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
286 self.meta = meta.into_option();
287 self
288 }
289}
290
291/// **UNSTABLE**
292///
293/// This capability is not part of the spec yet, and may be removed or changed at any point.
294///
295/// A plan represented as raw markdown content.
296#[cfg(feature = "unstable_plan_operations")]
297#[serde_as]
298#[skip_serializing_none]
299#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
300#[serde(rename_all = "camelCase")]
301#[non_exhaustive]
302pub struct PlanMarkdown {
303 /// The plan ID to update.
304 pub plan_id: PlanId,
305 /// Markdown content for the plan.
306 pub content: String,
307 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
308 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
309 /// these keys.
310 ///
311 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
312 #[serde_as(deserialize_as = "DefaultOnError")]
313 #[schemars(extend("x-deserialize-default-on-error" = true))]
314 #[serde(default)]
315 #[serde(rename = "_meta")]
316 pub meta: Option<Meta>,
317}
318
319#[cfg(feature = "unstable_plan_operations")]
320impl PlanMarkdown {
321 /// Builds [`PlanMarkdown`] with the required fields set; optional fields start unset or empty.
322 #[must_use]
323 pub fn new(plan_id: impl Into<PlanId>, content: impl Into<String>) -> Self {
324 Self {
325 plan_id: plan_id.into(),
326 content: content.into(),
327 meta: None,
328 }
329 }
330
331 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
332 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
333 /// these keys.
334 ///
335 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
336 #[must_use]
337 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
338 self.meta = meta.into_option();
339 self
340 }
341}
342
343/// **UNSTABLE**
344///
345/// This capability is not part of the spec yet, and may be removed or changed at any point.
346///
347/// Removal notice for a plan identified by ID.
348#[cfg(feature = "unstable_plan_operations")]
349#[serde_as]
350#[skip_serializing_none]
351#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
352#[serde(rename_all = "camelCase")]
353#[non_exhaustive]
354pub struct PlanRemoved {
355 /// The plan ID to remove.
356 pub plan_id: PlanId,
357 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
358 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
359 /// these keys.
360 ///
361 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
362 #[serde_as(deserialize_as = "DefaultOnError")]
363 #[schemars(extend("x-deserialize-default-on-error" = true))]
364 #[serde(default)]
365 #[serde(rename = "_meta")]
366 pub meta: Option<Meta>,
367}
368
369#[cfg(feature = "unstable_plan_operations")]
370impl PlanRemoved {
371 /// Builds [`PlanRemoved`] with the required fields set; optional fields start unset or empty.
372 #[must_use]
373 pub fn new(plan_id: impl Into<PlanId>) -> Self {
374 Self {
375 plan_id: plan_id.into(),
376 meta: None,
377 }
378 }
379
380 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
381 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
382 /// these keys.
383 ///
384 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
385 #[must_use]
386 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
387 self.meta = meta.into_option();
388 self
389 }
390}
391
392/// **UNSTABLE**
393///
394/// This capability is not part of the spec yet, and may be removed or changed at any point.
395///
396/// Capabilities for receiving `plan_update` and `plan_removed` session updates.
397#[cfg(feature = "unstable_plan_operations")]
398#[serde_as]
399#[skip_serializing_none]
400#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
401#[serde(rename_all = "camelCase")]
402#[non_exhaustive]
403pub struct PlanCapabilities {
404 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
405 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
406 /// these keys.
407 ///
408 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
409 #[serde_as(deserialize_as = "DefaultOnError")]
410 #[schemars(extend("x-deserialize-default-on-error" = true))]
411 #[serde(default)]
412 #[serde(rename = "_meta")]
413 pub meta: Option<Meta>,
414}
415
416#[cfg(feature = "unstable_plan_operations")]
417impl PlanCapabilities {
418 /// Builds an empty [`PlanCapabilities`]; use builder methods to advertise supported sub-capabilities.
419 #[must_use]
420 pub fn new() -> Self {
421 Self::default()
422 }
423
424 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
425 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
426 /// these keys.
427 ///
428 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
429 #[must_use]
430 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
431 self.meta = meta.into_option();
432 self
433 }
434}
435
436/// A single entry in the execution plan.
437///
438/// Represents a task or goal that the assistant intends to accomplish
439/// as part of fulfilling the user's request.
440/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
441#[serde_as]
442#[skip_serializing_none]
443#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
444#[serde(rename_all = "camelCase")]
445#[non_exhaustive]
446pub struct PlanEntry {
447 /// Human-readable description of what this task aims to accomplish.
448 pub content: String,
449 /// The relative importance of this task.
450 /// Used to indicate which tasks are most critical to the overall goal.
451 pub priority: PlanEntryPriority,
452 /// Current execution status of this task.
453 pub status: PlanEntryStatus,
454 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
455 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
456 /// these keys.
457 ///
458 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
459 #[serde_as(deserialize_as = "DefaultOnError")]
460 #[schemars(extend("x-deserialize-default-on-error" = true))]
461 #[serde(default)]
462 #[serde(rename = "_meta")]
463 pub meta: Option<Meta>,
464}
465
466impl PlanEntry {
467 /// Builds [`PlanEntry`] with the required fields set; optional fields start unset or empty.
468 #[must_use]
469 pub fn new(
470 content: impl Into<String>,
471 priority: PlanEntryPriority,
472 status: PlanEntryStatus,
473 ) -> Self {
474 Self {
475 content: content.into(),
476 priority,
477 status,
478 meta: None,
479 }
480 }
481
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 #[must_use]
488 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
489 self.meta = meta.into_option();
490 self
491 }
492}
493
494/// Priority levels for plan entries.
495///
496/// Used to indicate the relative importance or urgency of different
497/// tasks in the execution plan.
498/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
499#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone, PartialEq, Eq)]
500#[serde(rename_all = "snake_case")]
501#[non_exhaustive]
502pub enum PlanEntryPriority {
503 /// High priority task - critical to the overall goal.
504 High,
505 /// Medium priority task - important but not critical.
506 Medium,
507 /// Low priority task - nice to have but not essential.
508 Low,
509}
510
511/// Status of a plan entry in the execution flow.
512///
513/// Tracks the lifecycle of each task from planning through completion.
514/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
515#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone, PartialEq, Eq)]
516#[serde(rename_all = "snake_case")]
517#[non_exhaustive]
518pub enum PlanEntryStatus {
519 /// The task has not started yet.
520 Pending,
521 /// The task is currently being worked on.
522 InProgress,
523 /// The task has been successfully completed.
524 Completed,
525}