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