agent_client_protocol/
acp.rs

1pub mod mcp_types;
2pub use mcp_types::*;
3
4use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc};
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9// New session
10
11pub const NEW_SESSION_TOOL_NAME: &str = "acp__new_session";
12
13#[derive(Debug, Serialize, Deserialize, JsonSchema)]
14#[serde(rename_all = "camelCase")]
15pub struct NewSessionToolArguments {
16    pub mcp_servers: HashMap<String, McpServerConfig>,
17    pub client_tools: ClientTools,
18    pub cwd: PathBuf,
19}
20
21// Load session
22
23pub const LOAD_SESSION_TOOL_NAME: &str = "acp__load_session";
24
25#[derive(Debug, Serialize, Deserialize, JsonSchema)]
26#[serde(rename_all = "camelCase")]
27pub struct LoadSessionToolArguments {
28    pub mcp_servers: HashMap<String, McpServerConfig>,
29    pub client_tools: ClientTools,
30    pub cwd: PathBuf,
31    pub session_id: SessionId,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
35#[serde(transparent)]
36pub struct SessionId(pub Arc<str>);
37
38impl Display for SessionId {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.0)
41    }
42}
43
44#[derive(Debug, Serialize, Deserialize, JsonSchema)]
45#[serde(rename_all = "camelCase")]
46pub struct McpServerConfig {
47    pub command: String,
48    pub args: Vec<String>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub env: Option<HashMap<String, String>>,
51    /// If provided, only the specified tools are enabled
52    #[serde(default)]
53    pub enabled_tools: Option<Vec<String>>,
54}
55
56#[derive(Debug, Serialize, Deserialize, JsonSchema)]
57#[serde(rename_all = "camelCase")]
58pub struct McpToolId {
59    pub mcp_server: String,
60    pub tool_name: String,
61}
62
63// Prompt
64
65pub const PROMPT_TOOL_NAME: &str = "acp__prompt";
66
67#[derive(Debug, Serialize, Deserialize, JsonSchema)]
68#[serde(rename_all = "camelCase")]
69pub struct PromptToolArguments {
70    pub session_id: SessionId,
71    pub prompt: Vec<ContentBlock>,
72}
73
74// Session updates
75
76#[derive(Debug, Serialize, Deserialize, JsonSchema)]
77#[serde(tag = "type", rename_all = "camelCase")]
78pub struct SessionNotification {
79    pub session_id: SessionId,
80    #[serde(flatten)]
81    pub update: SessionUpdate,
82}
83
84#[derive(Debug, Serialize, Deserialize, JsonSchema)]
85#[serde(tag = "type", rename_all = "camelCase")]
86pub enum SessionUpdate {
87    Started,
88    UserMessage(ContentBlock),
89    AgentMessage(ContentBlock),
90    AgentThought(ContentBlock),
91    ToolCall(ToolCall),
92    Plan(Plan),
93}
94
95#[derive(Debug, Serialize, Deserialize, JsonSchema)]
96#[serde(rename_all = "camelCase")]
97pub struct ToolCall {
98    pub id: ToolCallId,
99    pub label: String,
100    pub kind: ToolKind,
101    pub status: ToolCallStatus,
102    #[serde(skip_serializing_if = "Vec::is_empty")]
103    pub content: Vec<ToolCallContent>,
104    #[serde(default, skip_serializing_if = "Vec::is_empty")]
105    pub locations: Vec<ToolCallLocation>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
109#[serde(transparent)]
110pub struct ToolCallId(pub Arc<str>);
111
112#[derive(Debug, Serialize, Deserialize, JsonSchema)]
113#[serde(rename_all = "camelCase")]
114pub enum ToolKind {
115    Read,
116    Edit,
117    Search,
118    Execute,
119    Think,
120    Fetch,
121    Other,
122}
123
124#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
125#[serde(rename_all = "camelCase")]
126pub enum ToolCallStatus {
127    /// The tool call is currently running
128    InProgress,
129    /// The tool call completed successfully
130    Completed,
131    /// The tool call failed
132    Failed,
133}
134
135#[derive(Debug, Serialize, Deserialize, JsonSchema)]
136#[serde(untagged, rename_all = "camelCase")]
137// todo: should we just add "diff" to the ContentBlock type?
138pub enum ToolCallContent {
139    ContentBlock { content: ContentBlock },
140    Diff { diff: Diff },
141}
142
143impl<T: Into<ContentBlock>> From<T> for ToolCallContent {
144    fn from(content: T) -> Self {
145        ToolCallContent::ContentBlock {
146            content: content.into(),
147        }
148    }
149}
150
151impl From<Diff> for ToolCallContent {
152    fn from(diff: Diff) -> Self {
153        ToolCallContent::Diff { diff }
154    }
155}
156
157#[derive(Debug, Serialize, Deserialize, JsonSchema)]
158#[serde(rename_all = "camelCase")]
159pub struct Diff {
160    pub path: PathBuf,
161    pub old_text: Option<String>,
162    pub new_text: String,
163}
164
165#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
166#[serde(tag = "type", rename_all = "camelCase")]
167pub struct ToolCallLocation {
168    pub path: PathBuf,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub line: Option<u32>,
171}
172
173#[derive(Debug, Serialize, Deserialize, JsonSchema)]
174#[serde(rename_all = "camelCase")]
175pub struct Plan {
176    pub entries: Vec<PlanEntry>,
177}
178
179/// A single entry in the execution plan.
180///
181/// Represents a task or goal that the assistant intends to accomplish
182/// as part of fulfilling the user's request.
183#[derive(Debug, Serialize, Deserialize, JsonSchema)]
184#[serde(rename_all = "camelCase")]
185pub struct PlanEntry {
186    /// Description of what this task aims to accomplish
187    pub content: String,
188    /// Relative importance of this task
189    pub priority: PlanEntryPriority,
190    /// Current progress of this task
191    pub status: PlanEntryStatus,
192}
193
194/// Priority levels for plan entries.
195///
196/// Used to indicate the relative importance or urgency of different
197/// tasks in the execution plan.
198#[derive(Deserialize, Serialize, JsonSchema, Debug)]
199#[serde(rename_all = "snake_case")]
200pub enum PlanEntryPriority {
201    High,
202    Medium,
203    Low,
204}
205
206/// Status of a plan entry in the execution flow.
207///
208/// Tracks the lifecycle of each task from planning through completion.
209#[derive(Deserialize, Serialize, JsonSchema, Debug)]
210#[serde(rename_all = "snake_case")]
211pub enum PlanEntryStatus {
212    Pending,
213    InProgress,
214    Completed,
215}
216
217// Client tools
218
219#[derive(Debug, Serialize, Deserialize, JsonSchema)]
220#[serde(rename_all = "camelCase")]
221pub struct ClientTools {
222    pub confirm_permission: Option<McpToolId>,
223    pub write_text_file: Option<McpToolId>,
224    pub read_text_file: Option<McpToolId>,
225}
226
227// Permission
228
229#[derive(Debug, Serialize, Deserialize, JsonSchema)]
230#[serde(rename_all = "camelCase")]
231pub struct PermissionToolArguments {
232    pub session_id: SessionId,
233    pub tool_call: ToolCall,
234    pub options: Vec<PermissionOption>,
235}
236
237#[derive(Debug, Serialize, Deserialize, JsonSchema)]
238pub struct PermissionOption {
239    pub id: PermissionOptionId,
240    pub label: String,
241    pub kind: PermissionOptionKind,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
245#[serde(transparent)]
246pub struct PermissionOptionId(pub Arc<str>);
247
248#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
249#[serde(rename_all = "camelCase")]
250pub enum PermissionOptionKind {
251    AllowOnce,
252    AllowAlways,
253    RejectOnce,
254    RejectAlways,
255}
256
257#[derive(Debug, Serialize, Deserialize, JsonSchema)]
258#[serde(tag = "outcome", rename_all = "camelCase")]
259pub enum PermissionOutcome {
260    Canceled,
261    #[serde(rename_all = "camelCase")]
262    Selected {
263        option_id: PermissionOptionId,
264    },
265}
266
267// Write text file
268
269#[derive(Debug, Serialize, Deserialize, JsonSchema)]
270#[serde(rename_all = "camelCase")]
271pub struct WriteTextFileToolArguments {
272    pub session_id: SessionId,
273    pub path: PathBuf,
274    pub content: String,
275}
276
277// Read text file
278
279#[derive(Debug, Serialize, Deserialize, JsonSchema)]
280#[serde(rename_all = "camelCase")]
281pub struct ReadTextFileArguments {
282    pub session_id: SessionId,
283    pub path: PathBuf,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub line: Option<u32>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub limit: Option<u32>,
288}