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