1use std::path::PathBuf;
4
5use agent_client_protocol::schema::v2::{AuthMethod, Meta, SessionId, ToolCallUpdate};
6use agent_client_protocol::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse};
7use clankerdiff_protocol::client::ClientCommand;
8use clankerdiff_protocol::shared::{DocumentUpdate, Event};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11pub use utils::display_meta::{ToolDisplayMeta, ToolResultMeta};
12pub use utils::mcp_status::{McpServerAuthCapability, McpServerStatus, McpServerStatusEntry};
13
14use crate::meta::{from_meta, to_meta};
15
16pub const AETHER_META_NAMESPACE: &str = "contextbridge/aether";
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
20#[serde(rename_all = "camelCase")]
21pub struct RemoteServerInfo {
22 pub cwd: PathBuf,
23 pub session_id: Option<SessionId>,
24}
25
26impl RemoteServerInfo {
27 #[must_use]
28 pub fn to_meta(&self) -> Meta {
29 to_meta(&RemoteInitializationMeta { remote: Some(self.clone()) }, Some(AETHER_META_NAMESPACE))
30 .unwrap_or_default()
31 }
32
33 #[must_use]
34 pub fn from_meta(meta: Option<&Meta>) -> Option<Self> {
35 from_meta::<RemoteInitializationMeta>(meta, Some(AETHER_META_NAMESPACE)).remote
36 }
37}
38
39#[cfg(not(target_family = "wasm"))]
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonRpcNotification)]
42#[notification(method = "_aether/session_usage")]
43pub struct SessionUsageParams {
44 pub usage: llm::SessionUsageEvent,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, JsonRpcNotification)]
49#[notification(method = "_aether/context_cleared")]
50#[serde(rename_all = "camelCase")]
51pub struct ContextClearedParams {
52 pub session_id: SessionId,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, JsonRpcNotification)]
57#[notification(method = "_aether/auth_methods_updated")]
58#[serde(rename_all = "camelCase")]
59pub struct AuthMethodsUpdatedParams {
60 pub auth_methods: Vec<AuthMethod>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcRequest)]
65#[request(method = "_aether/prompt_search", response = PromptSearchResponse)]
66#[serde(rename_all = "camelCase")]
67pub struct PromptSearchParams {
68 pub query: String,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub limit: Option<usize>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcResponse)]
75#[serde(rename_all = "camelCase")]
76pub struct PromptSearchResponse {
77 pub query: String,
78 pub results: Vec<PromptSearchResult>,
79 pub truncated: bool,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct PromptSearchResult {
89 pub session_id: String,
90 pub cwd: PathBuf,
91 pub session_created_at: String,
92 pub prompt: String,
93 pub match_start: usize,
94 pub match_end: usize,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcRequest)]
98#[request(method = "_aether/session_preview", response = SessionPreviewResponse)]
99#[serde(rename_all = "camelCase")]
100pub struct SessionPreviewParams {
101 pub session_id: String,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcResponse)]
105#[serde(rename_all = "camelCase")]
106pub struct SessionPreviewResponse {
107 pub session_id: String,
108 pub cwd: PathBuf,
109 pub created_at: String,
110 pub model: String,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub selected_mode: Option<String>,
113 pub transcript: Vec<SessionPreviewTurn>,
114 pub tool_call_count: usize,
115 pub truncated: bool,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(rename_all = "camelCase")]
120pub struct SessionPreviewTurn {
121 pub role: SessionPreviewRole,
122 pub text: String,
123}
124
125#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
126#[serde(rename_all = "camelCase")]
127pub enum SessionPreviewRole {
128 User,
129 Assistant,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcRequest)]
134#[request(method = "_aether/workspace_list", response = WorkspaceListResponse)]
135#[serde(rename_all = "camelCase")]
136pub struct WorkspaceListParams {
137 pub session_id: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcResponse)]
143#[serde(rename_all = "camelCase")]
144pub struct WorkspaceListResponse {
145 pub workspaces: Vec<WorkspaceEntry>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
149#[serde(rename_all = "camelCase")]
150pub struct WorkspaceEntry {
151 pub path: PathBuf,
152 pub is_current: bool,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcRequest)]
157#[request(method = "_aether/workspace_move", response = WorkspaceMoveResponse)]
158#[serde(rename_all = "camelCase")]
159pub struct WorkspaceMoveParams {
160 pub session_id: String,
161 pub target: WorkspaceMoveTarget,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165#[serde(tag = "kind", rename_all = "camelCase")]
166pub enum WorkspaceMoveTarget {
167 Existing { path: PathBuf },
168 New { name: String },
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcResponse)]
172#[serde(rename_all = "camelCase")]
173pub struct WorkspaceMoveResponse {
174 pub new_cwd: PathBuf,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
178#[serde(rename_all = "camelCase")]
179pub struct SessionDisplayMeta {
180 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub model: Option<String>,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub selected_mode: Option<String>,
184}
185
186impl SessionDisplayMeta {
187 #[must_use]
188 pub fn new(model: impl Into<String>, selected_mode: Option<String>) -> Self {
189 Self { model: Some(model.into()), selected_mode }
190 }
191
192 #[must_use]
193 pub fn to_meta(&self) -> Meta {
194 to_meta(self, Some(AETHER_META_NAMESPACE)).unwrap_or_default()
195 }
196
197 #[must_use]
198 pub fn from_meta(meta: Option<&Meta>) -> Self {
199 from_meta(meta, Some(AETHER_META_NAMESPACE))
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
204#[serde(rename_all = "camelCase")]
205pub struct AetherCapabilities {
206 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
207 pub prompt_search: bool,
208 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
209 pub session_preview: bool,
210 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
211 pub workspace_move: bool,
212}
213
214impl AetherCapabilities {
215 #[must_use]
216 pub fn to_meta(self) -> Meta {
217 to_meta(&self, Some(AETHER_META_NAMESPACE)).unwrap_or_default()
218 }
219
220 #[must_use]
221 pub fn from_meta(meta: Option<&Meta>) -> Self {
222 from_meta(meta, Some(AETHER_META_NAMESPACE))
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
227#[notification(method = "_aether/git_diff")]
228#[serde(rename_all = "camelCase")]
229pub struct GitDiffCommandPayload {
230 pub session_id: String,
231 #[serde(flatten)]
232 pub command: ClientCommand,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
236#[notification(method = "_aether/git_diff_event")]
237#[serde(rename_all = "camelCase")]
238pub struct GitDiffEventPayload {
239 pub session_id: SessionId,
240 #[serde(flatten)]
241 pub event: Event<DocumentUpdate>,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
245#[notification(method = "_aether/git_diff_close")]
246#[serde(rename_all = "camelCase")]
247pub struct GitDiffClosePayload {
248 pub session_id: String,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcRequest)]
252#[request(method = "_aether/workspace_status", response = WorkspaceStatusResponse)]
253#[serde(rename_all = "camelCase")]
254pub struct WorkspaceStatusPayload {
255 pub session_id: String,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcResponse)]
259#[serde(rename_all = "camelCase")]
260pub struct WorkspaceStatusResponse {
261 pub display_dir: String,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub git_ref: Option<String>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, JsonRpcNotification)]
268#[notification(method = "_aether/mcp_event")]
269#[serde(tag = "type", rename_all = "snake_case")]
270pub enum McpNotification {
271 ServerStatus { servers: Vec<McpServerStatusEntry> },
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonRpcNotification)]
276#[notification(method = "_aether/mcp_request")]
277#[serde(tag = "type", rename_all = "snake_case", rename_all_fields = "camelCase")]
278pub enum McpRequest {
279 Authenticate { session_id: String, server_name: String },
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, JsonRpcNotification)]
286#[notification(method = "_aether/sub_agent_progress")]
287#[serde(rename_all = "camelCase")]
288pub struct SubAgentProgressParams {
289 pub session_id: SessionId,
290 pub parent_tool_id: String,
291 pub task_id: String,
292 pub agent_name: String,
293 pub event: SubAgentEvent,
294}
295
296#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
297#[serde(tag = "type", rename_all = "snake_case")]
298pub enum SubAgentEvent {
299 Started,
300 ToolCallUpdate(Box<ToolCallUpdate>),
301 Done,
302}
303
304#[derive(Default, Serialize, Deserialize)]
305struct RemoteInitializationMeta {
306 remote: Option<RemoteServerInfo>,
307}