codex_protocol.rs (8621B)
1 //! JSON-RPC serde types for the Codex app-server protocol. 2 //! 3 //! The Codex app-server (`codex app-server --listen stdio://`) communicates via 4 //! JSONL (one JSON object per line) over stdin/stdout. It uses a JSON-RPC-like 5 //! schema but does NOT include the `"jsonrpc":"2.0"` header. 6 7 #![allow(dead_code)] // Protocol fields are defined for completeness; not all are read yet. 8 9 use serde::{Deserialize, Serialize}; 10 use serde_json::Value; 11 12 // --------------------------------------------------------------------------- 13 // Generic JSON-RPC envelope 14 // --------------------------------------------------------------------------- 15 16 /// Outgoing request or notification (client → server). 17 #[derive(Debug, Serialize)] 18 pub struct RpcRequest<P: Serialize> { 19 /// Present for requests that expect a response; absent for notifications. 20 #[serde(skip_serializing_if = "Option::is_none")] 21 pub id: Option<u64>, 22 pub method: &'static str, 23 pub params: P, 24 } 25 26 /// Incoming message from the server. Could be a response, notification, or 27 /// request (for bidirectional approval). 28 #[derive(Debug, Deserialize)] 29 pub struct RpcMessage { 30 /// Present on responses and server→client requests. 31 pub id: Option<u64>, 32 /// Present on notifications and server→client requests. 33 pub method: Option<String>, 34 /// Present on successful responses. 35 pub result: Option<Value>, 36 /// Present on error responses. 37 pub error: Option<RpcError>, 38 /// Present on notifications and server→client requests. 39 pub params: Option<Value>, 40 } 41 42 #[derive(Debug, Deserialize)] 43 pub struct RpcError { 44 pub code: i64, 45 pub message: String, 46 pub data: Option<Value>, 47 } 48 49 // --------------------------------------------------------------------------- 50 // Outgoing (client → server) 51 // --------------------------------------------------------------------------- 52 53 /// `initialize` params 54 #[derive(Debug, Serialize)] 55 #[serde(rename_all = "camelCase")] 56 pub struct InitializeParams { 57 pub client_info: ClientInfo, 58 pub capabilities: Value, // empty object for now 59 } 60 61 #[derive(Debug, Serialize)] 62 pub struct ClientInfo { 63 pub name: String, 64 pub version: String, 65 } 66 67 /// `thread/start` params 68 #[derive(Debug, Serialize)] 69 #[serde(rename_all = "camelCase")] 70 pub struct ThreadStartParams { 71 #[serde(skip_serializing_if = "Option::is_none")] 72 pub model: Option<String>, 73 #[serde(skip_serializing_if = "Option::is_none")] 74 pub cwd: Option<String>, 75 #[serde(skip_serializing_if = "Option::is_none")] 76 pub approval_policy: Option<String>, 77 } 78 79 /// `thread/resume` params 80 #[derive(Debug, Serialize)] 81 #[serde(rename_all = "camelCase")] 82 pub struct ThreadResumeParams { 83 pub thread_id: String, 84 } 85 86 /// `thread/compact/start` params 87 #[derive(Debug, Serialize)] 88 #[serde(rename_all = "camelCase")] 89 pub struct ThreadCompactParams { 90 pub thread_id: String, 91 } 92 93 /// `turn/start` params 94 #[derive(Debug, Serialize)] 95 #[serde(rename_all = "camelCase")] 96 pub struct TurnStartParams { 97 pub thread_id: String, 98 pub input: Vec<TurnInput>, 99 #[serde(skip_serializing_if = "Option::is_none")] 100 pub model: Option<String>, 101 #[serde(skip_serializing_if = "Option::is_none")] 102 pub effort: Option<String>, 103 } 104 105 #[derive(Debug, Serialize)] 106 #[serde(tag = "type")] 107 pub enum TurnInput { 108 #[serde(rename = "text")] 109 Text { text: String }, 110 } 111 112 /// `turn/interrupt` params 113 #[derive(Debug, Serialize)] 114 #[serde(rename_all = "camelCase")] 115 pub struct TurnInterruptParams { 116 pub thread_id: String, 117 pub turn_id: String, 118 } 119 120 /// Response to an approval request (client → server). 121 #[derive(Debug, Serialize)] 122 pub struct ApprovalResponse { 123 pub decision: ApprovalDecision, 124 } 125 126 #[derive(Debug, Serialize, Clone, Copy)] 127 #[serde(rename_all = "lowercase")] 128 pub enum ApprovalDecision { 129 Accept, 130 Decline, 131 Cancel, 132 } 133 134 // --------------------------------------------------------------------------- 135 // Incoming (server → client) 136 // --------------------------------------------------------------------------- 137 138 /// Result of `thread/start` 139 #[derive(Debug, Deserialize)] 140 pub struct ThreadStartResult { 141 pub thread: ThreadInfo, 142 pub model: Option<String>, 143 } 144 145 #[derive(Debug, Deserialize)] 146 pub struct ThreadInfo { 147 pub id: String, 148 } 149 150 /// `item/started` params 151 #[derive(Debug, Deserialize)] 152 #[serde(rename_all = "camelCase")] 153 pub struct ItemStartedParams { 154 /// The kind of item: "agentMessage", "commandExecution", "fileChange", 155 /// "collabAgentToolCall", "contextCompaction", etc. 156 #[serde(rename = "type")] 157 pub item_type: String, 158 /// Unique item ID 159 pub item_id: Option<String>, 160 /// For collabAgentToolCall: agent name/description 161 pub name: Option<String>, 162 /// For commandExecution: the command being run 163 pub command: Option<String>, 164 /// For fileChange: the file path 165 pub file_path: Option<String>, 166 } 167 168 /// `item/agentMessage/delta` params — a streaming text token 169 #[derive(Debug, Deserialize)] 170 pub struct AgentMessageDeltaParams { 171 pub delta: String, 172 } 173 174 /// `item/completed` params — an item has finished 175 #[derive(Debug, Deserialize)] 176 #[serde(rename_all = "camelCase")] 177 pub struct ItemCompletedParams { 178 #[serde(rename = "type")] 179 pub item_type: String, 180 pub item_id: Option<String>, 181 /// For commandExecution: the command that was run 182 pub command: Option<String>, 183 /// For commandExecution: exit code 184 pub exit_code: Option<i32>, 185 /// For commandExecution: stdout/stderr output 186 pub output: Option<String>, 187 /// For fileChange: the file path 188 pub file_path: Option<String>, 189 /// For fileChange: the diff 190 pub diff: Option<String>, 191 /// For fileChange: kind of change (create, edit, delete) 192 pub kind: Option<Value>, 193 /// For collabAgentToolCall: result text 194 pub result: Option<String>, 195 /// For contextCompaction: token info 196 pub pre_tokens: Option<u64>, 197 /// For agentMessage: full content 198 pub content: Option<String>, 199 } 200 201 /// `item/commandExecution/requestApproval` params — server asks client to approve a command. 202 /// 203 /// In V2, `command` and `cwd` are optional. When absent the command can be 204 /// reconstructed from `proposed_execpolicy_amendment` (an argv vec). 205 #[derive(Debug, Deserialize)] 206 #[serde(rename_all = "camelCase")] 207 pub struct CommandApprovalParams { 208 /// The command string (present in V2 when the Command presentation is used). 209 #[serde(default)] 210 pub command: Option<String>, 211 #[serde(default)] 212 pub cwd: Option<String>, 213 /// Reason the approval is needed (e.g. "Write outside workspace"). 214 #[serde(default)] 215 pub reason: Option<String>, 216 /// Proposed execpolicy amendment — an argv array that can serve as a 217 /// fallback when `command` is absent. 218 #[serde(default)] 219 pub proposed_execpolicy_amendment: Option<Vec<String>>, 220 } 221 222 impl CommandApprovalParams { 223 /// Best-effort command string for display / auto-accept evaluation. 224 pub fn command_string(&self) -> String { 225 if let Some(ref cmd) = self.command { 226 return cmd.clone(); 227 } 228 if let Some(ref argv) = self.proposed_execpolicy_amendment { 229 return argv.join(" "); 230 } 231 "unknown command".to_string() 232 } 233 } 234 235 /// `item/fileChange/requestApproval` params — server asks client to approve a file change. 236 /// 237 /// In V2, the params are minimal (just item/thread/turn ids + reason). 238 /// `file_path`, `diff`, and `kind` may be absent. 239 #[derive(Debug, Deserialize)] 240 #[serde(rename_all = "camelCase")] 241 pub struct FileChangeApprovalParams { 242 #[serde(default)] 243 pub file_path: Option<String>, 244 #[serde(default)] 245 pub diff: Option<String>, 246 #[serde(default)] 247 pub kind: Option<Value>, 248 /// Reason the approval is needed. 249 #[serde(default)] 250 pub reason: Option<String>, 251 /// Root directory the agent is requesting write access for. 252 #[serde(default)] 253 pub grant_root: Option<String>, 254 } 255 256 /// `turn/completed` params 257 #[derive(Debug, Deserialize)] 258 #[serde(rename_all = "camelCase")] 259 pub struct TurnCompletedParams { 260 /// "completed" or "failed" 261 pub status: String, 262 pub turn_id: Option<String>, 263 pub error: Option<String>, 264 } 265 266 /// `thread/tokenUsage/updated` params 267 #[derive(Debug, Deserialize)] 268 #[serde(rename_all = "camelCase")] 269 pub struct TokenUsageParams { 270 pub token_usage: TokenUsage, 271 } 272 273 #[derive(Debug, Deserialize)] 274 #[serde(rename_all = "camelCase")] 275 pub struct TokenUsage { 276 pub total: TokenBreakdown, 277 } 278 279 #[derive(Debug, Deserialize)] 280 #[serde(rename_all = "camelCase")] 281 pub struct TokenBreakdown { 282 pub input_tokens: i64, 283 pub output_tokens: i64, 284 }