notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }