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, Default)]
219#[serde(untagged)]
220pub enum RpcId {
221 String(String),
222 Number(i64),
223 #[default]
224 Null,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct SpawnParams {
234 pub task_id: String,
236 pub prompt: String,
238 #[serde(default)]
240 pub working_dir: Option<String>,
241 #[serde(default)]
243 pub harness: Option<String>,
244 #[serde(default)]
246 pub model: Option<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct SpawnTaskParams {
252 pub task_id: String,
254 #[serde(default)]
256 pub tag: Option<String>,
257 #[serde(default)]
259 pub harness: Option<String>,
260 #[serde(default)]
262 pub model: Option<String>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, Default)]
267pub struct ListTasksParams {
268 #[serde(default)]
270 pub tag: Option<String>,
271 #[serde(default)]
273 pub status: Option<String>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct GetTaskParams {
279 pub task_id: String,
281 #[serde(default)]
283 pub tag: Option<String>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct SetStatusParams {
289 pub task_id: String,
291 pub status: String,
293 #[serde(default)]
295 pub tag: Option<String>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize, Default)]
300pub struct NextTaskParams {
301 #[serde(default)]
303 pub tag: Option<String>,
304 #[serde(default)]
306 pub all_tags: bool,
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_parse_request() {
315 let json = r#"{"jsonrpc": "2.0", "method": "spawn", "params": {"task_id": "1", "prompt": "test"}, "id": 1}"#;
316 let req: RpcRequest = serde_json::from_str(json).unwrap();
317 assert_eq!(req.method, "spawn");
318 assert_eq!(req.id, Some(RpcId::Number(1)));
319 }
320
321 #[test]
322 fn test_parse_notification() {
323 let json = r#"{"jsonrpc": "2.0", "method": "cancel", "params": {"task_id": "1"}}"#;
324 let req: RpcRequest = serde_json::from_str(json).unwrap();
325 assert!(req.is_notification());
326 }
327
328 #[test]
329 fn test_serialize_response() {
330 let resp = RpcResponse::success(RpcId::Number(1), serde_json::json!({"status": "ok"}));
331 let json = serde_json::to_string(&resp).unwrap();
332 assert!(json.contains("\"result\""));
333 assert!(!json.contains("\"error\""));
334 }
335
336 #[test]
337 fn test_serialize_notification() {
338 let notif = RpcNotification::agent_started("task:1");
339 let json = serde_json::to_string(¬if).unwrap();
340 assert!(json.contains("agent.started"));
341 assert!(json.contains("task:1"));
342 }
343
344 #[test]
345 fn test_error_codes() {
346 let err = RpcError::method_not_found("unknown");
347 assert_eq!(err.code, -32601);
348 }
349
350 #[test]
351 fn test_spawn_params() {
352 let json = r#"{"task_id": "1", "prompt": "do something", "harness": "claude"}"#;
353 let params: SpawnParams = serde_json::from_str(json).unwrap();
354 assert_eq!(params.task_id, "1");
355 assert_eq!(params.prompt, "do something");
356 assert_eq!(params.harness, Some("claude".to_string()));
357 }
358}