agent_teams/backend/
codex_protocol.rs1use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Serialize)]
21pub struct JsonRpcRequest {
22 pub id: serde_json::Value,
24 pub method: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub params: Option<serde_json::Value>,
29}
30
31impl JsonRpcRequest {
32 pub fn new(id: u64, method: &str, params: Option<serde_json::Value>) -> Self {
34 Self {
35 id: serde_json::Value::Number(id.into()),
36 method: method.into(),
37 params,
38 }
39 }
40}
41
42#[derive(Debug, Serialize)]
44pub struct JsonRpcClientNotification {
45 pub method: String,
47}
48
49impl JsonRpcClientNotification {
50 pub fn new(method: &str) -> Self {
51 Self {
52 method: method.into(),
53 }
54 }
55}
56
57#[derive(Debug, Deserialize)]
59pub struct JsonRpcResponse {
60 pub id: serde_json::Value,
62 #[serde(default)]
64 pub result: Option<serde_json::Value>,
65 #[serde(default)]
67 pub error: Option<JsonRpcError>,
68}
69
70#[derive(Debug, Deserialize)]
72pub struct JsonRpcError {
73 pub code: i64,
75 pub message: String,
77 #[serde(default)]
79 pub data: Option<serde_json::Value>,
80}
81
82impl std::fmt::Display for JsonRpcError {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "JSON-RPC error {}: {}", self.code, self.message)
85 }
86}
87
88#[derive(Debug, Deserialize)]
90pub struct JsonRpcNotification {
91 pub method: String,
93 #[serde(default)]
95 pub params: Option<serde_json::Value>,
96}
97
98#[derive(Debug, Deserialize)]
104#[serde(untagged)]
105pub enum JsonRpcMessage {
106 Response(JsonRpcResponse),
108 Notification(JsonRpcNotification),
110}
111
112pub const METHOD_INITIALIZE: &str = "initialize";
118pub const METHOD_INITIALIZED: &str = "initialized";
120pub const METHOD_THREAD_START: &str = "thread/start";
122pub const METHOD_TURN_START: &str = "turn/start";
124pub const METHOD_TURN_INTERRUPT: &str = "turn/interrupt";
126
127pub const EVENT_THREAD_STARTED: &str = "thread/started";
133pub const EVENT_TURN_STARTED: &str = "turn/started";
135pub const EVENT_TURN_COMPLETED: &str = "turn/completed";
137pub const EVENT_ITEM_STARTED: &str = "item/started";
139pub const EVENT_AGENT_MESSAGE_DELTA: &str = "item/agentMessage/delta";
141pub const EVENT_ITEM_COMPLETED: &str = "item/completed";
143pub const EVENT_COMMAND_OUTPUT_DELTA: &str = "item/commandExecution/outputDelta";
145pub const EVENT_ERROR: &str = "error";
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn serialize_request() {
154 let req = JsonRpcRequest::new(
155 1,
156 METHOD_INITIALIZE,
157 Some(serde_json::json!({"clientInfo": {"name": "test", "version": "0.1.0"}})),
158 );
159 let json = serde_json::to_string(&req).unwrap();
160 assert!(json.contains(r#""id":1"#));
161 assert!(json.contains(r#""method":"initialize""#));
162 assert!(!json.contains("jsonrpc"));
164 }
165
166 #[test]
167 fn serialize_client_notification() {
168 let notif = JsonRpcClientNotification::new(METHOD_INITIALIZED);
169 let json = serde_json::to_string(¬if).unwrap();
170 assert!(json.contains(r#""method":"initialized""#));
171 assert!(!json.contains("id"));
172 }
173
174 #[test]
175 fn deserialize_response_ok() {
176 let json = r#"{"id":1,"result":{"userAgent":"agent-teams/0.87.0"}}"#;
177 let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
178 assert!(resp.error.is_none());
179 assert_eq!(
180 resp.result.unwrap()["userAgent"].as_str().unwrap(),
181 "agent-teams/0.87.0"
182 );
183 }
184
185 #[test]
186 fn deserialize_response_err() {
187 let json = r#"{"id":2,"error":{"code":-32600,"message":"bad request"}}"#;
188 let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
189 assert!(resp.result.is_none());
190 let err = resp.error.unwrap();
191 assert_eq!(err.code, -32600);
192 }
193
194 #[test]
195 fn deserialize_notification() {
196 let json =
197 r#"{"method":"item/agentMessage/delta","params":{"delta":"hello","threadId":"t1","turnId":"0","itemId":"i1"}}"#;
198 let notif: JsonRpcNotification = serde_json::from_str(json).unwrap();
199 assert_eq!(notif.method, EVENT_AGENT_MESSAGE_DELTA);
200 assert_eq!(notif.params.unwrap()["delta"].as_str().unwrap(), "hello");
201 }
202
203 #[test]
204 fn deserialize_envelope_response() {
205 let json = r#"{"id":1,"result":null}"#;
206 let msg: JsonRpcMessage = serde_json::from_str(json).unwrap();
207 assert!(matches!(msg, JsonRpcMessage::Response(_)));
208 }
209
210 #[test]
211 fn deserialize_envelope_notification() {
212 let json = r#"{"method":"turn/completed","params":{"threadId":"t1","turn":{"id":"0","items":[],"status":"completed"}}}"#;
213 let msg: JsonRpcMessage = serde_json::from_str(json).unwrap();
214 assert!(matches!(msg, JsonRpcMessage::Notification(_)));
215 }
216
217 #[test]
218 fn deserialize_thread_start_response() {
219 let json = r#"{"id":2,"result":{"thread":{"id":"abc-123","preview":"","modelProvider":"openai","createdAt":1700000000,"path":"/tmp","source":"cli","turns":[],"cwd":"/tmp","cliVersion":"0.87.0"},"model":"gpt-4","modelProvider":"openai","cwd":"/tmp","approvalPolicy":"never","sandbox":{"type":"workspaceWrite"}}}"#;
220 let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
221 let result = resp.result.unwrap();
222 let thread_id = result["thread"]["id"].as_str().unwrap();
223 assert_eq!(thread_id, "abc-123");
224 }
225
226 #[test]
227 fn deserialize_turn_start_response() {
228 let json = r#"{"id":3,"result":{"turn":{"id":"0","items":[],"status":"inProgress","error":null}}}"#;
229 let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
230 let result = resp.result.unwrap();
231 let turn_id = result["turn"]["id"].as_str().unwrap();
232 assert_eq!(turn_id, "0");
233 }
234}