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