Skip to main content

phi_ext/pxb/
types.rs

1//! Wire constants, frame types, and lifecycle event codes.
2
3/// Negotiated in Hello / HelloAck; bump only for incompatible renames.
4pub const PROTOCOL_VERSION: u16 = 1;
5
6/// Frame magic: `P X B` + version byte.
7pub const MAGIC: [u8; 4] = *b"PXB\x01";
8
9/// Fixed frame header length.
10pub const HEADER_SIZE: usize = 16;
11
12/// Maximum payload accepted from a peer (16 MiB).
13pub const MAX_PAYLOAD: usize = 16 << 20;
14
15// Message types. Ext→Host are 1–99; Host→Ext are 100–199.
16// Append-only: new types take the next number in their range.
17pub const TYPE_HELLO: u16 = 1;
18pub const TYPE_READY: u16 = 2;
19pub const TYPE_REGISTER_COMMAND: u16 = 3;
20pub const TYPE_REGISTER_TOOL: u16 = 4;
21pub const TYPE_SUBSCRIBE: u16 = 5;
22pub const TYPE_COMMAND_RESPONSE: u16 = 6;
23pub const TYPE_TOOL_RESULT: u16 = 7;
24pub const TYPE_INTERCEPT_RESPONSE: u16 = 8;
25pub const TYPE_SHUTDOWN_ACK: u16 = 9;
26pub const TYPE_NOTIFY: u16 = 10;
27pub const TYPE_HOST_REQUEST: u16 = 11;
28pub const TYPE_TOOL_DETAIL_RESULT: u16 = 12;
29
30pub const TYPE_HELLO_ACK: u16 = 100;
31pub const TYPE_COMMAND_INVOKED: u16 = 101;
32pub const TYPE_TOOL_INVOKE: u16 = 102;
33pub const TYPE_EVENT: u16 = 103;
34pub const TYPE_INTERCEPT: u16 = 104;
35pub const TYPE_SHUTDOWN: u16 = 105;
36pub const TYPE_HOST_RESULT: u16 = 106;
37pub const TYPE_SESSION_META: u16 = 107;
38pub const TYPE_TOOL_DETAIL_INVOKE: u16 = 108;
39
40/// Flag bits in the header.
41pub const FLAG_HAS_ID: u16 = 1 << 0; // id field is meaningful (RPC correlation)
42
43/// Capability bits advertised in Hello.
44pub const CAP_COMMANDS: u32 = 1 << 0;
45pub const CAP_TOOLS: u32 = 1 << 1;
46pub const CAP_EVENTS: u32 = 1 << 2;
47pub const CAP_INTERCEPT: u32 = 1 << 3;
48
49/// Frame types with an explicit [`Unknown`](FrameType::Unknown) arm so peers
50/// that do not understand a type still skip the frame by length.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum FrameType {
53    Hello,
54    Ready,
55    RegisterCommand,
56    RegisterTool,
57    Subscribe,
58    CommandResponse,
59    ToolResult,
60    InterceptResponse,
61    ShutdownAck,
62    Notify,
63    HostRequest,
64    ToolDetailResult,
65    HelloAck,
66    CommandInvoked,
67    ToolInvoke,
68    Event,
69    Intercept,
70    Shutdown,
71    HostResult,
72    SessionMeta,
73    ToolDetailInvoke,
74    Unknown(u16),
75}
76
77impl FrameType {
78    pub fn from_u16(v: u16) -> Self {
79        match v {
80            TYPE_HELLO => Self::Hello,
81            TYPE_READY => Self::Ready,
82            TYPE_REGISTER_COMMAND => Self::RegisterCommand,
83            TYPE_REGISTER_TOOL => Self::RegisterTool,
84            TYPE_SUBSCRIBE => Self::Subscribe,
85            TYPE_COMMAND_RESPONSE => Self::CommandResponse,
86            TYPE_TOOL_RESULT => Self::ToolResult,
87            TYPE_INTERCEPT_RESPONSE => Self::InterceptResponse,
88            TYPE_SHUTDOWN_ACK => Self::ShutdownAck,
89            TYPE_NOTIFY => Self::Notify,
90            TYPE_HOST_REQUEST => Self::HostRequest,
91            TYPE_TOOL_DETAIL_RESULT => Self::ToolDetailResult,
92            TYPE_HELLO_ACK => Self::HelloAck,
93            TYPE_COMMAND_INVOKED => Self::CommandInvoked,
94            TYPE_TOOL_INVOKE => Self::ToolInvoke,
95            TYPE_EVENT => Self::Event,
96            TYPE_INTERCEPT => Self::Intercept,
97            TYPE_SHUTDOWN => Self::Shutdown,
98            TYPE_HOST_RESULT => Self::HostResult,
99            TYPE_SESSION_META => Self::SessionMeta,
100            TYPE_TOOL_DETAIL_INVOKE => Self::ToolDetailInvoke,
101            other => Self::Unknown(other),
102        }
103    }
104
105    pub fn code(self) -> u16 {
106        match self {
107            Self::Hello => TYPE_HELLO,
108            Self::Ready => TYPE_READY,
109            Self::RegisterCommand => TYPE_REGISTER_COMMAND,
110            Self::RegisterTool => TYPE_REGISTER_TOOL,
111            Self::Subscribe => TYPE_SUBSCRIBE,
112            Self::CommandResponse => TYPE_COMMAND_RESPONSE,
113            Self::ToolResult => TYPE_TOOL_RESULT,
114            Self::InterceptResponse => TYPE_INTERCEPT_RESPONSE,
115            Self::ShutdownAck => TYPE_SHUTDOWN_ACK,
116            Self::Notify => TYPE_NOTIFY,
117            Self::HostRequest => TYPE_HOST_REQUEST,
118            Self::ToolDetailResult => TYPE_TOOL_DETAIL_RESULT,
119            Self::HelloAck => TYPE_HELLO_ACK,
120            Self::CommandInvoked => TYPE_COMMAND_INVOKED,
121            Self::ToolInvoke => TYPE_TOOL_INVOKE,
122            Self::Event => TYPE_EVENT,
123            Self::Intercept => TYPE_INTERCEPT,
124            Self::Shutdown => TYPE_SHUTDOWN,
125            Self::HostResult => TYPE_HOST_RESULT,
126            Self::SessionMeta => TYPE_SESSION_META,
127            Self::ToolDetailInvoke => TYPE_TOOL_DETAIL_INVOKE,
128            Self::Unknown(v) => v,
129        }
130    }
131}
132
133/// Lifecycle event codes (compact on the wire; strings only at SDK edges).
134/// Append-only: never reuse a code. Unknown codes are ignored by peers that
135/// did not Subscribe to them.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum Event {
138    ToolCall,
139    ToolResult,
140    ToolExecStart,
141    ToolExecEnd,
142    SessionStart,
143    SessionShutdown,
144    SessionBeforeSwitch,
145    BeforeAgentStart,
146    AgentStart,
147    AgentEnd,
148    TurnStart,
149    TurnEnd,
150    UserInput,
151    TurnStopping,
152    SessionCompact,
153    PaneAction,
154    Unknown(u16),
155}
156
157impl Event {
158    /// Maps a wire code to an [`Event`]; unrecognized codes stay
159    /// [`Unknown`](Event::Unknown).
160    pub fn from_code(code: u16) -> Self {
161        match code {
162            1 => Self::ToolCall,
163            2 => Self::ToolResult,
164            3 => Self::ToolExecStart,
165            4 => Self::ToolExecEnd,
166            5 => Self::SessionStart,
167            6 => Self::SessionShutdown,
168            7 => Self::SessionBeforeSwitch,
169            8 => Self::BeforeAgentStart,
170            9 => Self::AgentStart,
171            10 => Self::AgentEnd,
172            11 => Self::TurnStart,
173            12 => Self::TurnEnd,
174            13 => Self::UserInput,
175            14 => Self::TurnStopping,
176            15 => Self::SessionCompact,
177            16 => Self::PaneAction,
178            other => Self::Unknown(other),
179        }
180    }
181
182    pub fn code(self) -> u16 {
183        match self {
184            Self::ToolCall => 1,
185            Self::ToolResult => 2,
186            Self::ToolExecStart => 3,
187            Self::ToolExecEnd => 4,
188            Self::SessionStart => 5,
189            Self::SessionShutdown => 6,
190            Self::SessionBeforeSwitch => 7,
191            Self::BeforeAgentStart => 8,
192            Self::AgentStart => 9,
193            Self::AgentEnd => 10,
194            Self::TurnStart => 11,
195            Self::TurnEnd => 12,
196            Self::UserInput => 13,
197            Self::TurnStopping => 14,
198            Self::SessionCompact => 15,
199            Self::PaneAction => 16,
200            Self::Unknown(v) => v,
201        }
202    }
203
204    /// Public `ext` event name, or `""` for unknown codes.
205    pub fn name(self) -> &'static str {
206        match self {
207            Self::ToolCall => "tool_call",
208            Self::ToolResult => "tool_result",
209            Self::ToolExecStart => "tool_execution_start",
210            Self::ToolExecEnd => "tool_execution_end",
211            Self::SessionStart => "session_start",
212            Self::SessionShutdown => "session_shutdown",
213            Self::SessionBeforeSwitch => "session_before_switch",
214            Self::BeforeAgentStart => "before_agent_start",
215            Self::AgentStart => "agent_start",
216            Self::AgentEnd => "agent_end",
217            Self::TurnStart => "turn_start",
218            Self::TurnEnd => "turn_end",
219            Self::UserInput => "user_input",
220            Self::TurnStopping => "turn_stopping",
221            Self::SessionCompact => "session_compact",
222            Self::PaneAction => "pane_action",
223            Self::Unknown(_) => "",
224        }
225    }
226
227    /// Parses a public event name; unknown names yield
228    /// [`Unknown(0)`](Event::Unknown) (the "no event" code).
229    pub fn from_name(name: &str) -> Self {
230        match name {
231            "tool_call" => Self::ToolCall,
232            "tool_result" => Self::ToolResult,
233            "tool_execution_start" => Self::ToolExecStart,
234            "tool_execution_end" => Self::ToolExecEnd,
235            "session_start" => Self::SessionStart,
236            "session_shutdown" => Self::SessionShutdown,
237            "session_before_switch" => Self::SessionBeforeSwitch,
238            "before_agent_start" => Self::BeforeAgentStart,
239            "agent_start" => Self::AgentStart,
240            "agent_end" => Self::AgentEnd,
241            "turn_start" => Self::TurnStart,
242            "turn_end" => Self::TurnEnd,
243            "user_input" => Self::UserInput,
244            "turn_stopping" => Self::TurnStopping,
245            "session_compact" => Self::SessionCompact,
246            "pane_action" => Self::PaneAction,
247            _ => Self::Unknown(0),
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn frame_type_roundtrip() {
258        for v in 1..=19 {
259            assert_eq!(FrameType::from_u16(v).code(), v);
260        }
261        assert_eq!(FrameType::from_u16(999), FrameType::Unknown(999));
262    }
263
264    #[test]
265    fn event_name_and_code_roundtrip() {
266        for code in 1..=16 {
267            let ev = Event::from_code(code);
268            assert_eq!(ev.code(), code);
269            assert_eq!(Event::from_name(ev.name()), ev);
270        }
271        assert_eq!(Event::from_code(0), Event::Unknown(0));
272        assert_eq!(Event::from_name("nope"), Event::Unknown(0));
273    }
274}