1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub const JSONRPC_VERSION: &str = "2.0";
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct RpcRequest {
12 pub jsonrpc: String,
14 pub method: String,
16 #[serde(default)]
18 pub params: Value,
19 pub id: Option<RpcId>,
21}
22
23impl RpcRequest {
24 pub fn is_notification(&self) -> bool {
26 self.id.is_none()
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct RpcResponse {
33 pub jsonrpc: String,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub result: Option<Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub error: Option<RpcError>,
41 pub id: RpcId,
43}
44
45impl RpcResponse {
46 pub fn success(id: RpcId, result: Value) -> Self {
48 Self {
49 jsonrpc: JSONRPC_VERSION.to_string(),
50 result: Some(result),
51 error: None,
52 id,
53 }
54 }
55
56 pub fn error(id: RpcId, error: RpcError) -> Self {
58 Self {
59 jsonrpc: JSONRPC_VERSION.to_string(),
60 result: None,
61 error: Some(error),
62 id,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RpcNotification {
70 pub jsonrpc: String,
72 pub method: String,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub params: Option<Value>,
77}
78
79impl RpcNotification {
80 pub fn new(method: impl Into<String>, params: Value) -> Self {
82 Self {
83 jsonrpc: JSONRPC_VERSION.to_string(),
84 method: method.into(),
85 params: Some(params),
86 }
87 }
88
89 pub fn agent_started(task_id: &str) -> Self {
91 Self::new(
92 "agent.started",
93 serde_json::json!({
94 "task_id": task_id
95 }),
96 )
97 }
98
99 pub fn agent_output(task_id: &str, line: &str) -> Self {
101 Self::new(
102 "agent.output",
103 serde_json::json!({
104 "task_id": task_id,
105 "line": line
106 }),
107 )
108 }
109
110 pub fn agent_completed(
112 task_id: &str,
113 success: bool,
114 exit_code: Option<i32>,
115 duration_ms: u64,
116 ) -> Self {
117 Self::new(
118 "agent.completed",
119 serde_json::json!({
120 "task_id": task_id,
121 "success": success,
122 "exit_code": exit_code,
123 "duration_ms": duration_ms
124 }),
125 )
126 }
127
128 pub fn agent_spawn_failed(task_id: &str, error: &str) -> Self {
130 Self::new(
131 "agent.spawn_failed",
132 serde_json::json!({
133 "task_id": task_id,
134 "error": error
135 }),
136 )
137 }
138
139 pub fn server_ready(version: &str) -> Self {
141 Self::new(
142 "server.ready",
143 serde_json::json!({
144 "version": version
145 }),
146 )
147 }
148
149 pub fn server_shutdown() -> Self {
151 Self::new("server.shutdown", serde_json::json!({}))
152 }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct RpcError {
158 pub code: i32,
160 pub message: String,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub data: Option<Value>,
165}
166
167impl RpcError {
168 pub fn new(code: i32, message: impl Into<String>) -> Self {
170 Self {
171 code,
172 message: message.into(),
173 data: None,
174 }
175 }
176
177 pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
179 Self {
180 code,
181 message: message.into(),
182 data: Some(data),
183 }
184 }
185
186 pub fn parse_error(msg: &str) -> Self {
188 Self::new(-32700, format!("Parse error: {}", msg))
189 }
190
191 pub fn invalid_request(msg: &str) -> Self {
192 Self::new(-32600, format!("Invalid request: {}", msg))
193 }
194
195 pub fn method_not_found(method: &str) -> Self {
196 Self::new(-32601, format!("Method not found: {}", method))
197 }
198
199 pub fn invalid_params(msg: &str) -> Self {
200 Self::new(-32602, format!("Invalid params: {}", msg))
201 }
202
203 pub fn internal_error(msg: &str) -> Self {
204 Self::new(-32603, format!("Internal error: {}", msg))
205 }
206
207 pub fn spawn_failed(msg: &str) -> Self {
209 Self::new(-32001, format!("Agent spawn failed: {}", msg))
210 }
211
212 pub fn task_not_found(task_id: &str) -> Self {
213 Self::new(-32002, format!("Task not found: {}", task_id))
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
219#[serde(untagged)]
220pub enum RpcId {
221 String(String),
222 Number(i64),
223 Null,
224}
225
226impl Default for RpcId {
227 fn default() -> Self {
228 Self::Null
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct SpawnParams {
239 pub task_id: String,
241 pub prompt: String,
243 #[serde(default)]
245 pub working_dir: Option<String>,
246 #[serde(default)]
248 pub harness: Option<String>,
249 #[serde(default)]
251 pub model: Option<String>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct SpawnTaskParams {
257 pub task_id: String,
259 #[serde(default)]
261 pub tag: Option<String>,
262 #[serde(default)]
264 pub harness: Option<String>,
265 #[serde(default)]
267 pub model: Option<String>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, Default)]
272pub struct ListTasksParams {
273 #[serde(default)]
275 pub tag: Option<String>,
276 #[serde(default)]
278 pub status: Option<String>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct GetTaskParams {
284 pub task_id: String,
286 #[serde(default)]
288 pub tag: Option<String>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct SetStatusParams {
294 pub task_id: String,
296 pub status: String,
298 #[serde(default)]
300 pub tag: Option<String>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize, Default)]
305pub struct NextTaskParams {
306 #[serde(default)]
308 pub tag: Option<String>,
309 #[serde(default)]
311 pub all_tags: bool,
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn test_parse_request() {
320 let json = r#"{"jsonrpc": "2.0", "method": "spawn", "params": {"task_id": "1", "prompt": "test"}, "id": 1}"#;
321 let req: RpcRequest = serde_json::from_str(json).unwrap();
322 assert_eq!(req.method, "spawn");
323 assert_eq!(req.id, Some(RpcId::Number(1)));
324 }
325
326 #[test]
327 fn test_parse_notification() {
328 let json = r#"{"jsonrpc": "2.0", "method": "cancel", "params": {"task_id": "1"}}"#;
329 let req: RpcRequest = serde_json::from_str(json).unwrap();
330 assert!(req.is_notification());
331 }
332
333 #[test]
334 fn test_serialize_response() {
335 let resp = RpcResponse::success(RpcId::Number(1), serde_json::json!({"status": "ok"}));
336 let json = serde_json::to_string(&resp).unwrap();
337 assert!(json.contains("\"result\""));
338 assert!(!json.contains("\"error\""));
339 }
340
341 #[test]
342 fn test_serialize_notification() {
343 let notif = RpcNotification::agent_started("task:1");
344 let json = serde_json::to_string(¬if).unwrap();
345 assert!(json.contains("agent.started"));
346 assert!(json.contains("task:1"));
347 }
348
349 #[test]
350 fn test_error_codes() {
351 let err = RpcError::method_not_found("unknown");
352 assert_eq!(err.code, -32601);
353 }
354
355 #[test]
356 fn test_spawn_params() {
357 let json = r#"{"task_id": "1", "prompt": "do something", "harness": "claude"}"#;
358 let params: SpawnParams = serde_json::from_str(json).unwrap();
359 assert_eq!(params.task_id, "1");
360 assert_eq!(params.prompt, "do something");
361 assert_eq!(params.harness, Some("claude".to_string()));
362 }
363}