1use super::codec::Error;
8use super::fields::{walk_fields, FieldReader, FieldWriter, WIRE_BYTES, WIRE_U64};
9
10const F_HELLO_NAME: u16 = 1;
13const F_HELLO_VERSION: u16 = 2;
14const F_HELLO_CAPS: u16 = 3;
15const F_HELLO_PROTOCOL: u16 = 4;
16
17const F_ACK_PROTOCOL: u16 = 1;
18const F_ACK_PHI_VERSION: u16 = 2;
19const F_ACK_CWD: u16 = 3;
20const F_ACK_SESSION_ID: u16 = 4;
21const F_ACK_EXT_DIR: u16 = 5;
22
23const F_REG_CMD_NAME: u16 = 1;
24const F_REG_CMD_DESC: u16 = 2;
25
26const F_REG_TOOL_NAME: u16 = 1;
27const F_REG_TOOL_DESC: u16 = 2;
28const F_REG_TOOL_SCHEMA: u16 = 3;
29const F_REG_TOOL_TIMEOUT_SEC: u16 = 4;
30
31const F_SUB_EVENTS: u16 = 1;
32const F_SUB_INTERCEPT: u16 = 2;
33
34const F_CMD_INV_NAME: u16 = 1;
35const F_CMD_INV_ARGS: u16 = 2;
36
37const F_CMD_RES_OK: u16 = 1;
38const F_CMD_RES_ERROR: u16 = 2;
39const F_CMD_RES_NOTIFY: u16 = 3;
40const F_CMD_RES_SUBMIT: u16 = 4;
41
42const F_TOOL_INV_NAME: u16 = 1;
43const F_TOOL_INV_ARGS: u16 = 2;
44
45const F_TOOL_RES_CONTENT: u16 = 1;
46const F_TOOL_RES_DETAIL: u16 = 2;
47const F_TOOL_RES_OUTPUT: u16 = 3;
48const F_TOOL_RES_IS_ERROR: u16 = 4;
49const F_TOOL_RES_ERROR: u16 = 5;
50
51const F_IX_REQ_EVENT: u16 = 1;
52const F_IX_REQ_TOOL_NAME: u16 = 2;
53const F_IX_REQ_TOOL_CALL_ID: u16 = 3;
54const F_IX_REQ_INPUT: u16 = 4;
55const F_IX_REQ_CONTENT: u16 = 5;
56const F_IX_REQ_IS_ERROR: u16 = 6;
57const F_IX_REQ_ERR_TEXT: u16 = 7;
58const F_IX_REQ_PROMPT: u16 = 8;
59const F_IX_REQ_REASON: u16 = 9;
60const F_IX_REQ_TARGET_ID: u16 = 10;
61const F_IX_REQ_TURN_INDEX: u16 = 11;
62
63const F_IX_RES_BLOCK: u16 = 1;
64const F_IX_RES_STOP: u16 = 2;
65const F_IX_RES_CANCEL: u16 = 3;
66const F_IX_RES_REASON: u16 = 4;
67const F_IX_RES_INPUT: u16 = 5;
68const F_IX_RES_CONTENT: u16 = 6;
69const F_IX_RES_CONTEXT: u16 = 7;
70const F_IX_RES_SYS_APPEND: u16 = 8;
71const F_IX_RES_TOAST: u16 = 9;
72const F_IX_RES_HANDLED: u16 = 10;
73const F_IX_RES_PROMPT: u16 = 11;
74const F_IX_RES_CONTINUE: u16 = 12;
75
76const F_EV_EVENT: u16 = 1;
77const F_EV_TOOL_NAME: u16 = 2;
78const F_EV_TOOL_CALL_ID: u16 = 3;
79const F_EV_INPUT: u16 = 4;
80const F_EV_IS_ERROR: u16 = 5;
81const F_EV_PROMPT: u16 = 6;
82const F_EV_REASON: u16 = 7;
83const F_EV_TURN_INDEX: u16 = 8;
84const F_EV_SESSION_ID: u16 = 9;
85const F_EV_PREVIOUS_SESSION_ID: u16 = 10;
86const F_EV_TARGET_SESSION_ID: u16 = 11;
87
88const F_NOTIFY_LEVEL: u16 = 1;
89const F_NOTIFY_MESSAGE: u16 = 2;
90const F_NOTIFY_STATUS: u16 = 3;
91const F_NOTIFY_STATUS_SET: u16 = 4;
92
93const F_HOST_REQ_METHOD: u16 = 1;
94const F_HOST_REQ_ARG: u16 = 2;
95
96const F_HOST_RES_OK: u16 = 1;
97const F_HOST_RES_ERROR: u16 = 2;
98const F_HOST_RES_BODY: u16 = 3;
99
100const F_META_SESSION_ID: u16 = 1;
101const F_META_CWD: u16 = 2;
102
103fn take_u64(kind: u8, fr: &mut FieldReader<'_>) -> Result<u64, Error> {
104 if kind != WIRE_U64 {
105 fr.skip(kind)?;
106 return Err(Error::BadWire);
107 }
108 fr.u64()
109}
110
111fn take_bytes<'a>(kind: u8, fr: &mut FieldReader<'a>) -> Result<&'a [u8], Error> {
112 if kind != WIRE_BYTES {
113 fr.skip(kind)?;
114 return Err(Error::BadWire);
115 }
116 fr.bytes()
117}
118
119fn take_string(kind: u8, fr: &mut FieldReader<'_>) -> Result<String, Error> {
122 Ok(String::from_utf8_lossy(take_bytes(kind, fr)?).into_owned())
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Default)]
127pub struct Hello {
128 pub name: String,
129 pub version: String,
130 pub caps: u32,
131 pub protocol: u16,
132}
133
134pub fn encode_hello(h: &Hello) -> Vec<u8> {
135 let mut fw = FieldWriter::new();
136 fw.put_string(F_HELLO_NAME, &h.name);
137 fw.put_string(F_HELLO_VERSION, &h.version);
138 fw.put_u32(F_HELLO_CAPS, h.caps);
139 fw.put_u16(F_HELLO_PROTOCOL, h.protocol);
140 fw.into_vec()
141}
142
143pub fn decode_hello(b: &[u8]) -> Result<Hello, Error> {
144 let mut h = Hello::default();
145 walk_fields(b, |tag, kind, fr| {
146 match tag {
147 F_HELLO_NAME => h.name = take_string(kind, fr)?,
148 F_HELLO_VERSION => h.version = take_string(kind, fr)?,
149 F_HELLO_CAPS => h.caps = take_u64(kind, fr)? as u32,
150 F_HELLO_PROTOCOL => h.protocol = take_u64(kind, fr)? as u16,
151 _ => fr.skip(kind)?,
152 }
153 Ok(())
154 })?;
155 Ok(h)
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Default)]
160pub struct HelloAck {
161 pub protocol: u16,
162 pub phi_version: String,
163 pub cwd: String,
164 pub session_id: String,
165 pub extension_dir: String,
166}
167
168pub fn encode_hello_ack(h: &HelloAck) -> Vec<u8> {
169 let mut fw = FieldWriter::new();
170 fw.put_u16(F_ACK_PROTOCOL, h.protocol);
171 fw.put_string(F_ACK_PHI_VERSION, &h.phi_version);
172 fw.put_string(F_ACK_CWD, &h.cwd);
173 fw.put_string(F_ACK_SESSION_ID, &h.session_id);
174 fw.put_string(F_ACK_EXT_DIR, &h.extension_dir);
175 fw.into_vec()
176}
177
178pub fn decode_hello_ack(b: &[u8]) -> Result<HelloAck, Error> {
179 let mut h = HelloAck::default();
180 walk_fields(b, |tag, kind, fr| {
181 match tag {
182 F_ACK_PROTOCOL => h.protocol = take_u64(kind, fr)? as u16,
183 F_ACK_PHI_VERSION => h.phi_version = take_string(kind, fr)?,
184 F_ACK_CWD => h.cwd = take_string(kind, fr)?,
185 F_ACK_SESSION_ID => h.session_id = take_string(kind, fr)?,
186 F_ACK_EXT_DIR => h.extension_dir = take_string(kind, fr)?,
187 _ => fr.skip(kind)?,
188 }
189 Ok(())
190 })?;
191 Ok(h)
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Default)]
196pub struct RegisterCommand {
197 pub name: String,
198 pub description: String,
199}
200
201pub fn encode_register_command(r: &RegisterCommand) -> Vec<u8> {
202 let mut fw = FieldWriter::new();
203 fw.put_string(F_REG_CMD_NAME, &r.name);
204 fw.put_string(F_REG_CMD_DESC, &r.description);
205 fw.into_vec()
206}
207
208pub fn decode_register_command(b: &[u8]) -> Result<RegisterCommand, Error> {
209 let mut r = RegisterCommand::default();
210 walk_fields(b, |tag, kind, fr| {
211 match tag {
212 F_REG_CMD_NAME => r.name = take_string(kind, fr)?,
213 F_REG_CMD_DESC => r.description = take_string(kind, fr)?,
214 _ => fr.skip(kind)?,
215 }
216 Ok(())
217 })?;
218 Ok(r)
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Default)]
223pub struct RegisterTool {
224 pub name: String,
225 pub description: String,
226 pub schema_json: Vec<u8>,
227 pub timeout_sec: u32,
230}
231
232pub fn encode_register_tool(r: &RegisterTool) -> Vec<u8> {
233 let mut fw = FieldWriter::new();
234 fw.put_string(F_REG_TOOL_NAME, &r.name);
235 fw.put_string(F_REG_TOOL_DESC, &r.description);
236 fw.put_bytes(F_REG_TOOL_SCHEMA, &r.schema_json);
237 if r.timeout_sec > 0 {
238 fw.put_u32(F_REG_TOOL_TIMEOUT_SEC, r.timeout_sec);
239 }
240 fw.into_vec()
241}
242
243pub fn decode_register_tool(b: &[u8]) -> Result<RegisterTool, Error> {
244 let mut r = RegisterTool::default();
245 walk_fields(b, |tag, kind, fr| {
246 match tag {
247 F_REG_TOOL_NAME => r.name = take_string(kind, fr)?,
248 F_REG_TOOL_DESC => r.description = take_string(kind, fr)?,
249 F_REG_TOOL_SCHEMA => r.schema_json = take_bytes(kind, fr)?.to_vec(),
250 F_REG_TOOL_TIMEOUT_SEC => r.timeout_sec = take_u64(kind, fr)? as u32,
251 _ => fr.skip(kind)?,
252 }
253 Ok(())
254 })?;
255 Ok(r)
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Default)]
260pub struct Subscribe {
261 pub events: Vec<u16>,
262 pub intercept: Vec<u16>,
263}
264
265pub fn encode_subscribe(s: &Subscribe) -> Vec<u8> {
266 let mut fw = FieldWriter::new();
267 fw.put_u16s(F_SUB_EVENTS, &s.events);
268 fw.put_u16s(F_SUB_INTERCEPT, &s.intercept);
269 fw.into_vec()
270}
271
272pub fn decode_subscribe(b: &[u8]) -> Result<Subscribe, Error> {
273 let mut s = Subscribe::default();
274 walk_fields(b, |tag, kind, fr| {
275 match tag {
276 F_SUB_EVENTS => s.events = decode_u16s(take_bytes(kind, fr)?)?,
277 F_SUB_INTERCEPT => s.intercept = decode_u16s(take_bytes(kind, fr)?)?,
278 _ => fr.skip(kind)?,
279 }
280 Ok(())
281 })?;
282 Ok(s)
283}
284
285fn decode_u16s(p: &[u8]) -> Result<Vec<u16>, Error> {
286 if p.len() < 2 {
288 return Err(Error::Truncated);
289 }
290 let n = u16::from_le_bytes([p[0], p[1]]) as usize;
291 if p.len() < 2 + n * 2 {
292 return Err(Error::Truncated);
293 }
294 let mut out = Vec::with_capacity(n);
295 for i in 0..n {
296 let off = 2 + i * 2;
297 out.push(u16::from_le_bytes([p[off], p[off + 1]]));
298 }
299 Ok(out)
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Default)]
304pub struct CommandInvoked {
305 pub name: String,
306 pub args: String,
307}
308
309pub fn encode_command_invoked(c: &CommandInvoked) -> Vec<u8> {
310 let mut fw = FieldWriter::new();
311 fw.put_string(F_CMD_INV_NAME, &c.name);
312 fw.put_string(F_CMD_INV_ARGS, &c.args);
313 fw.into_vec()
314}
315
316pub fn decode_command_invoked(b: &[u8]) -> Result<CommandInvoked, Error> {
317 let mut c = CommandInvoked::default();
318 walk_fields(b, |tag, kind, fr| {
319 match tag {
320 F_CMD_INV_NAME => c.name = take_string(kind, fr)?,
321 F_CMD_INV_ARGS => c.args = take_string(kind, fr)?,
322 _ => fr.skip(kind)?,
323 }
324 Ok(())
325 })?;
326 Ok(c)
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Default)]
331pub struct CommandResponse {
332 pub ok: bool,
333 pub error: String,
334 pub notify: String,
335 pub submit: String,
336}
337
338pub fn encode_command_response(c: &CommandResponse) -> Vec<u8> {
339 let mut fw = FieldWriter::new();
340 fw.put_bool(F_CMD_RES_OK, c.ok);
341 fw.put_string(F_CMD_RES_ERROR, &c.error);
342 fw.put_string(F_CMD_RES_NOTIFY, &c.notify);
343 fw.put_string(F_CMD_RES_SUBMIT, &c.submit);
344 fw.into_vec()
345}
346
347pub fn decode_command_response(b: &[u8]) -> Result<CommandResponse, Error> {
348 let mut c = CommandResponse::default();
349 walk_fields(b, |tag, kind, fr| {
350 match tag {
351 F_CMD_RES_OK => c.ok = take_u64(kind, fr)? != 0,
352 F_CMD_RES_ERROR => c.error = take_string(kind, fr)?,
353 F_CMD_RES_NOTIFY => c.notify = take_string(kind, fr)?,
354 F_CMD_RES_SUBMIT => c.submit = take_string(kind, fr)?,
355 _ => fr.skip(kind)?,
356 }
357 Ok(())
358 })?;
359 Ok(c)
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Default)]
364pub struct ToolInvoke {
365 pub name: String,
366 pub args: Vec<u8>,
367}
368
369pub fn encode_tool_invoke(t: &ToolInvoke) -> Vec<u8> {
370 let mut fw = FieldWriter::new();
371 fw.put_string(F_TOOL_INV_NAME, &t.name);
372 fw.put_bytes(F_TOOL_INV_ARGS, &t.args);
373 fw.into_vec()
374}
375
376pub fn decode_tool_invoke(b: &[u8]) -> Result<ToolInvoke, Error> {
377 let mut t = ToolInvoke::default();
378 walk_fields(b, |tag, kind, fr| {
379 match tag {
380 F_TOOL_INV_NAME => t.name = take_string(kind, fr)?,
381 F_TOOL_INV_ARGS => t.args = take_bytes(kind, fr)?.to_vec(),
382 _ => fr.skip(kind)?,
383 }
384 Ok(())
385 })?;
386 Ok(t)
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, Default)]
391pub struct ToolResultMsg {
392 pub content: String,
393 pub detail: String,
394 pub output: String,
395 pub is_error: bool,
396 pub error: String,
397}
398
399pub fn encode_tool_result(t: &ToolResultMsg) -> Vec<u8> {
400 let mut fw = FieldWriter::new();
401 fw.put_string(F_TOOL_RES_CONTENT, &t.content);
402 fw.put_string(F_TOOL_RES_DETAIL, &t.detail);
403 fw.put_string(F_TOOL_RES_OUTPUT, &t.output);
404 fw.put_bool(F_TOOL_RES_IS_ERROR, t.is_error);
405 fw.put_string(F_TOOL_RES_ERROR, &t.error);
406 fw.into_vec()
407}
408
409pub fn decode_tool_result(b: &[u8]) -> Result<ToolResultMsg, Error> {
410 let mut t = ToolResultMsg::default();
411 walk_fields(b, |tag, kind, fr| {
412 match tag {
413 F_TOOL_RES_CONTENT => t.content = take_string(kind, fr)?,
414 F_TOOL_RES_DETAIL => t.detail = take_string(kind, fr)?,
415 F_TOOL_RES_OUTPUT => t.output = take_string(kind, fr)?,
416 F_TOOL_RES_IS_ERROR => t.is_error = take_u64(kind, fr)? != 0,
417 F_TOOL_RES_ERROR => t.error = take_string(kind, fr)?,
418 _ => fr.skip(kind)?,
419 }
420 Ok(())
421 })?;
422 Ok(t)
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Default)]
427pub struct InterceptReq {
428 pub event: u16,
429 pub tool_name: String,
430 pub tool_call_id: String,
431 pub input: Vec<u8>,
432 pub content: String,
433 pub is_error: bool,
434 pub err_text: String,
435 pub prompt: String,
436 pub reason: String,
437 pub target_id: String,
438 pub turn_index: u32,
439}
440
441pub fn encode_intercept_req(r: &InterceptReq) -> Vec<u8> {
442 let mut fw = FieldWriter::new();
443 fw.put_u16(F_IX_REQ_EVENT, r.event);
444 fw.put_string(F_IX_REQ_TOOL_NAME, &r.tool_name);
445 fw.put_string(F_IX_REQ_TOOL_CALL_ID, &r.tool_call_id);
446 fw.put_bytes(F_IX_REQ_INPUT, &r.input);
447 fw.put_string(F_IX_REQ_CONTENT, &r.content);
448 fw.put_bool(F_IX_REQ_IS_ERROR, r.is_error);
449 fw.put_string(F_IX_REQ_ERR_TEXT, &r.err_text);
450 fw.put_string(F_IX_REQ_PROMPT, &r.prompt);
451 fw.put_string(F_IX_REQ_REASON, &r.reason);
452 fw.put_string(F_IX_REQ_TARGET_ID, &r.target_id);
453 fw.put_u32(F_IX_REQ_TURN_INDEX, r.turn_index);
454 fw.into_vec()
455}
456
457pub fn decode_intercept_req(b: &[u8]) -> Result<InterceptReq, Error> {
458 let mut r = InterceptReq::default();
459 walk_fields(b, |tag, kind, fr| {
460 match tag {
461 F_IX_REQ_EVENT => r.event = take_u64(kind, fr)? as u16,
462 F_IX_REQ_TOOL_NAME => r.tool_name = take_string(kind, fr)?,
463 F_IX_REQ_TOOL_CALL_ID => r.tool_call_id = take_string(kind, fr)?,
464 F_IX_REQ_INPUT => r.input = take_bytes(kind, fr)?.to_vec(),
465 F_IX_REQ_CONTENT => r.content = take_string(kind, fr)?,
466 F_IX_REQ_IS_ERROR => r.is_error = take_u64(kind, fr)? != 0,
467 F_IX_REQ_ERR_TEXT => r.err_text = take_string(kind, fr)?,
468 F_IX_REQ_PROMPT => r.prompt = take_string(kind, fr)?,
469 F_IX_REQ_REASON => r.reason = take_string(kind, fr)?,
470 F_IX_REQ_TARGET_ID => r.target_id = take_string(kind, fr)?,
471 F_IX_REQ_TURN_INDEX => r.turn_index = take_u64(kind, fr)? as u32,
472 _ => fr.skip(kind)?,
473 }
474 Ok(())
475 })?;
476 Ok(r)
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Default)]
481pub struct InterceptResp {
482 pub block: bool,
483 pub stop: bool,
484 pub cancel: bool,
485 pub handled: bool,
486 pub continue_: bool,
489 pub reason: String,
490 pub input: Vec<u8>,
491 pub content: String,
492 pub context: String,
493 pub system_prompt_append: String,
494 pub toast: String,
495 pub prompt: String,
496}
497
498pub fn encode_intercept_resp(r: &InterceptResp) -> Vec<u8> {
499 let mut fw = FieldWriter::new();
500 fw.put_bool(F_IX_RES_BLOCK, r.block);
501 fw.put_bool(F_IX_RES_STOP, r.stop);
502 fw.put_bool(F_IX_RES_CANCEL, r.cancel);
503 fw.put_string(F_IX_RES_REASON, &r.reason);
504 fw.put_bytes(F_IX_RES_INPUT, &r.input);
505 fw.put_string(F_IX_RES_CONTENT, &r.content);
506 fw.put_string(F_IX_RES_CONTEXT, &r.context);
507 fw.put_string(F_IX_RES_SYS_APPEND, &r.system_prompt_append);
508 fw.put_string(F_IX_RES_TOAST, &r.toast);
509 fw.put_bool(F_IX_RES_HANDLED, r.handled);
510 fw.put_string(F_IX_RES_PROMPT, &r.prompt);
511 fw.put_bool(F_IX_RES_CONTINUE, r.continue_);
512 fw.into_vec()
513}
514
515pub fn decode_intercept_resp(b: &[u8]) -> Result<InterceptResp, Error> {
516 let mut r = InterceptResp::default();
517 walk_fields(b, |tag, kind, fr| {
518 match tag {
519 F_IX_RES_BLOCK => r.block = take_u64(kind, fr)? != 0,
520 F_IX_RES_STOP => r.stop = take_u64(kind, fr)? != 0,
521 F_IX_RES_CANCEL => r.cancel = take_u64(kind, fr)? != 0,
522 F_IX_RES_HANDLED => r.handled = take_u64(kind, fr)? != 0,
523 F_IX_RES_CONTINUE => r.continue_ = take_u64(kind, fr)? != 0,
524 F_IX_RES_REASON => r.reason = take_string(kind, fr)?,
525 F_IX_RES_INPUT => r.input = take_bytes(kind, fr)?.to_vec(),
526 F_IX_RES_CONTENT => r.content = take_string(kind, fr)?,
527 F_IX_RES_CONTEXT => r.context = take_string(kind, fr)?,
528 F_IX_RES_SYS_APPEND => r.system_prompt_append = take_string(kind, fr)?,
529 F_IX_RES_TOAST => r.toast = take_string(kind, fr)?,
530 F_IX_RES_PROMPT => r.prompt = take_string(kind, fr)?,
531 _ => fr.skip(kind)?,
532 }
533 Ok(())
534 })?;
535 Ok(r)
536}
537
538#[derive(Debug, Clone, PartialEq, Eq, Default)]
540pub struct EventNotify {
541 pub event: u16,
542 pub tool_name: String,
543 pub tool_call_id: String,
544 pub input: Vec<u8>,
545 pub is_error: bool,
546 pub prompt: String,
547 pub reason: String,
548 pub turn_index: u32,
549 pub session_id: String,
550 pub previous_session_id: String,
551 pub target_session_id: String,
552}
553
554pub fn encode_event_notify(e: &EventNotify) -> Vec<u8> {
555 let mut fw = FieldWriter::new();
556 fw.put_u16(F_EV_EVENT, e.event);
557 fw.put_string(F_EV_TOOL_NAME, &e.tool_name);
558 fw.put_string(F_EV_TOOL_CALL_ID, &e.tool_call_id);
559 fw.put_bytes(F_EV_INPUT, &e.input);
560 fw.put_bool(F_EV_IS_ERROR, e.is_error);
561 fw.put_string(F_EV_PROMPT, &e.prompt);
562 fw.put_string(F_EV_REASON, &e.reason);
563 fw.put_u32(F_EV_TURN_INDEX, e.turn_index);
564 fw.put_string(F_EV_SESSION_ID, &e.session_id);
565 fw.put_string(F_EV_PREVIOUS_SESSION_ID, &e.previous_session_id);
566 fw.put_string(F_EV_TARGET_SESSION_ID, &e.target_session_id);
567 fw.into_vec()
568}
569
570pub fn decode_event_notify(b: &[u8]) -> Result<EventNotify, Error> {
571 let mut e = EventNotify::default();
572 walk_fields(b, |tag, kind, fr| {
573 match tag {
574 F_EV_EVENT => e.event = take_u64(kind, fr)? as u16,
575 F_EV_TOOL_NAME => e.tool_name = take_string(kind, fr)?,
576 F_EV_TOOL_CALL_ID => e.tool_call_id = take_string(kind, fr)?,
577 F_EV_INPUT => e.input = take_bytes(kind, fr)?.to_vec(),
578 F_EV_IS_ERROR => e.is_error = take_u64(kind, fr)? != 0,
579 F_EV_PROMPT => e.prompt = take_string(kind, fr)?,
580 F_EV_REASON => e.reason = take_string(kind, fr)?,
581 F_EV_TURN_INDEX => e.turn_index = take_u64(kind, fr)? as u32,
582 F_EV_SESSION_ID => e.session_id = take_string(kind, fr)?,
583 F_EV_PREVIOUS_SESSION_ID => e.previous_session_id = take_string(kind, fr)?,
584 F_EV_TARGET_SESSION_ID => e.target_session_id = take_string(kind, fr)?,
585 _ => fr.skip(kind)?,
586 }
587 Ok(())
588 })?;
589 Ok(e)
590}
591
592#[derive(Debug, Clone, PartialEq, Eq, Default)]
594pub struct NotifyMsg {
595 pub level: String,
596 pub message: String,
597 pub status: String,
598 pub status_set: bool,
599}
600
601pub fn encode_notify(n: &NotifyMsg) -> Vec<u8> {
602 let mut fw = FieldWriter::new();
603 fw.put_string(F_NOTIFY_LEVEL, &n.level);
604 fw.put_string(F_NOTIFY_MESSAGE, &n.message);
605 fw.put_string(F_NOTIFY_STATUS, &n.status);
606 fw.put_bool(F_NOTIFY_STATUS_SET, n.status_set);
607 fw.into_vec()
608}
609
610pub fn decode_notify(b: &[u8]) -> Result<NotifyMsg, Error> {
611 let mut n = NotifyMsg::default();
612 walk_fields(b, |tag, kind, fr| {
613 match tag {
614 F_NOTIFY_LEVEL => n.level = take_string(kind, fr)?,
615 F_NOTIFY_MESSAGE => n.message = take_string(kind, fr)?,
616 F_NOTIFY_STATUS => n.status = take_string(kind, fr)?,
617 F_NOTIFY_STATUS_SET => n.status_set = take_u64(kind, fr)? != 0,
618 _ => fr.skip(kind)?,
619 }
620 Ok(())
621 })?;
622 Ok(n)
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Default)]
627pub struct HostRequest {
628 pub method: String, pub arg: String,
630}
631
632pub fn encode_host_request(r: &HostRequest) -> Vec<u8> {
633 let mut fw = FieldWriter::new();
634 fw.put_string(F_HOST_REQ_METHOD, &r.method);
635 fw.put_string(F_HOST_REQ_ARG, &r.arg);
636 fw.into_vec()
637}
638
639pub fn decode_host_request(b: &[u8]) -> Result<HostRequest, Error> {
640 let mut r = HostRequest::default();
641 walk_fields(b, |tag, kind, fr| {
642 match tag {
643 F_HOST_REQ_METHOD => r.method = take_string(kind, fr)?,
644 F_HOST_REQ_ARG => r.arg = take_string(kind, fr)?,
645 _ => fr.skip(kind)?,
646 }
647 Ok(())
648 })?;
649 Ok(r)
650}
651
652#[derive(Debug, Clone, PartialEq, Eq, Default)]
654pub struct HostResult {
655 pub ok: bool,
656 pub error: String,
657 pub body: String,
658}
659
660pub fn encode_host_result(r: &HostResult) -> Vec<u8> {
661 let mut fw = FieldWriter::new();
662 fw.put_bool(F_HOST_RES_OK, r.ok);
663 fw.put_string(F_HOST_RES_ERROR, &r.error);
664 fw.put_string(F_HOST_RES_BODY, &r.body);
665 fw.into_vec()
666}
667
668pub fn decode_host_result(b: &[u8]) -> Result<HostResult, Error> {
669 let mut r = HostResult::default();
670 walk_fields(b, |tag, kind, fr| {
671 match tag {
672 F_HOST_RES_OK => r.ok = take_u64(kind, fr)? != 0,
673 F_HOST_RES_ERROR => r.error = take_string(kind, fr)?,
674 F_HOST_RES_BODY => r.body = take_string(kind, fr)?,
675 _ => fr.skip(kind)?,
676 }
677 Ok(())
678 })?;
679 Ok(r)
680}
681
682#[derive(Debug, Clone, PartialEq, Eq, Default)]
684pub struct SessionMeta {
685 pub session_id: String,
686 pub cwd: String,
687}
688
689pub fn encode_session_meta(m: &SessionMeta) -> Vec<u8> {
690 let mut fw = FieldWriter::new();
691 fw.put_string(F_META_SESSION_ID, &m.session_id);
692 fw.put_string(F_META_CWD, &m.cwd);
693 fw.into_vec()
694}
695
696pub fn decode_session_meta(b: &[u8]) -> Result<SessionMeta, Error> {
697 let mut m = SessionMeta::default();
698 walk_fields(b, |tag, kind, fr| {
699 match tag {
700 F_META_SESSION_ID => m.session_id = take_string(kind, fr)?,
701 F_META_CWD => m.cwd = take_string(kind, fr)?,
702 _ => fr.skip(kind)?,
703 }
704 Ok(())
705 })?;
706 Ok(m)
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712
713 #[test]
716 fn all_messages_roundtrip() {
717 type Reencode = fn(&[u8]) -> Result<Vec<u8>, Error>;
718 let cases: Vec<(Vec<u8>, Reencode)> = vec![
719 (
720 encode_hello(&Hello {
721 name: "greet".into(),
722 version: "1.0.0".into(),
723 caps: 3,
724 protocol: 1,
725 }),
726 |b| Ok(encode_hello(&decode_hello(b)?)),
727 ),
728 (
729 encode_hello_ack(&HelloAck {
730 protocol: 1,
731 phi_version: "v0.19.0".into(),
732 cwd: "/tmp".into(),
733 session_id: "s1".into(),
734 extension_dir: "/ext".into(),
735 }),
736 |b| Ok(encode_hello_ack(&decode_hello_ack(b)?)),
737 ),
738 (
739 encode_register_command(&RegisterCommand {
740 name: "hi".into(),
741 description: "Say hi".into(),
742 }),
743 |b| Ok(encode_register_command(&decode_register_command(b)?)),
744 ),
745 (
746 encode_register_tool(&RegisterTool {
747 name: "t".into(),
748 description: "d".into(),
749 schema_json: br#"{"type":"object"}"#.to_vec(),
750 timeout_sec: 120,
751 }),
752 |b| Ok(encode_register_tool(&decode_register_tool(b)?)),
753 ),
754 (
755 encode_subscribe(&Subscribe {
756 events: vec![5, 10],
757 intercept: vec![1, 2],
758 }),
759 |b| Ok(encode_subscribe(&decode_subscribe(b)?)),
760 ),
761 (
762 encode_command_invoked(&CommandInvoked {
763 name: "hi".into(),
764 args: "a b".into(),
765 }),
766 |b| Ok(encode_command_invoked(&decode_command_invoked(b)?)),
767 ),
768 (
769 encode_command_response(&CommandResponse {
770 ok: false,
771 error: "boom".into(),
772 notify: String::new(),
773 submit: "next".into(),
774 }),
775 |b| Ok(encode_command_response(&decode_command_response(b)?)),
776 ),
777 (
778 encode_tool_invoke(&ToolInvoke {
779 name: "t".into(),
780 args: br#"{"k":1}"#.to_vec(),
781 }),
782 |b| Ok(encode_tool_invoke(&decode_tool_invoke(b)?)),
783 ),
784 (
785 encode_tool_result(&ToolResultMsg {
786 content: "c".into(),
787 detail: "d".into(),
788 output: "o".into(),
789 is_error: true,
790 error: "e".into(),
791 }),
792 |b| Ok(encode_tool_result(&decode_tool_result(b)?)),
793 ),
794 (
795 encode_intercept_req(&InterceptReq {
796 event: 1,
797 tool_name: "bash".into(),
798 tool_call_id: "c1".into(),
799 input: br#"{"command":"ls"}"#.to_vec(),
800 content: "out".into(),
801 is_error: false,
802 err_text: String::new(),
803 prompt: "p".into(),
804 reason: "r".into(),
805 target_id: "t2".into(),
806 turn_index: 3,
807 }),
808 |b| Ok(encode_intercept_req(&decode_intercept_req(b)?)),
809 ),
810 (
811 encode_intercept_resp(&InterceptResp {
812 block: true,
813 stop: false,
814 cancel: false,
815 handled: true,
816 continue_: true,
817 reason: "r".into(),
818 input: b"in".to_vec(),
819 content: "c".into(),
820 context: "ctx".into(),
821 system_prompt_append: "sys".into(),
822 toast: "t".into(),
823 prompt: "p".into(),
824 }),
825 |b| Ok(encode_intercept_resp(&decode_intercept_resp(b)?)),
826 ),
827 (
828 encode_event_notify(&EventNotify {
829 event: 5,
830 tool_name: "t".into(),
831 tool_call_id: "c".into(),
832 input: b"i".to_vec(),
833 is_error: true,
834 prompt: "p".into(),
835 reason: "r".into(),
836 turn_index: 2,
837 session_id: "s".into(),
838 previous_session_id: "ps".into(),
839 target_session_id: "ts".into(),
840 }),
841 |b| Ok(encode_event_notify(&decode_event_notify(b)?)),
842 ),
843 (
844 encode_notify(&NotifyMsg {
845 level: "info".into(),
846 message: "Hello".into(),
847 status: "st".into(),
848 status_set: true,
849 }),
850 |b| Ok(encode_notify(&decode_notify(b)?)),
851 ),
852 (
853 encode_host_request(&HostRequest {
854 method: "confirm".into(),
855 arg: r#"{"Title":"t"}"#.into(),
856 }),
857 |b| Ok(encode_host_request(&decode_host_request(b)?)),
858 ),
859 (
860 encode_host_result(&HostResult {
861 ok: true,
862 error: String::new(),
863 body: "b".into(),
864 }),
865 |b| Ok(encode_host_result(&decode_host_result(b)?)),
866 ),
867 (
868 encode_session_meta(&SessionMeta {
869 session_id: "s2".into(),
870 cwd: "/x".into(),
871 }),
872 |b| Ok(encode_session_meta(&decode_session_meta(b)?)),
873 ),
874 ];
875
876 for (bytes, reencode) in cases {
877 assert_eq!(reencode(&bytes).unwrap(), bytes);
878 }
879 }
880
881 #[test]
882 fn decode_skips_unknown_tags() {
883 let mut w = FieldWriter::new();
884 w.put_string(1, "name");
885 w.put_string(200, "future field"); w.put_string(2, "1.0.0");
887 let h = decode_hello(&w.into_vec()).unwrap();
888 assert_eq!(h.name, "name");
889 assert_eq!(h.version, "1.0.0");
890 }
891}