1use crate::enums::Status;
2use crate::error::TypeError;
3use crate::fields::{
4 decode_bytes_value, decode_field_header, decode_varint_value, encode_bytes_field,
5 encode_varint_field, skip_field,
6};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct ToolResultBlock {
28 pub tool_name: String,
29 pub status: Status,
30 pub content: Vec<u8>,
31 pub schema_hint: Option<String>,
34}
35
36impl ToolResultBlock {
37 pub fn encode_body(&self) -> Vec<u8> {
39 let mut buf = Vec::new();
40 encode_bytes_field(&mut buf, 1, self.tool_name.as_bytes());
41 encode_varint_field(&mut buf, 2, u64::from(self.status.to_wire_byte()));
42 encode_bytes_field(&mut buf, 3, &self.content);
43 if let Some(ref hint) = self.schema_hint {
44 encode_bytes_field(&mut buf, 4, hint.as_bytes());
45 }
46 buf
47 }
48
49 pub fn decode_body(mut buf: &[u8]) -> Result<Self, TypeError> {
51 let mut tool_name: Option<String> = None;
52 let mut status: Option<Status> = None;
53 let mut content: Option<Vec<u8>> = None;
54 let mut schema_hint: Option<String> = None;
55
56 while !buf.is_empty() {
57 let (header, n) = decode_field_header(buf)?;
58 buf = &buf[n..];
59
60 match header.field_id {
61 1 => {
62 let (data, n) = decode_bytes_value(buf)?;
63 buf = &buf[n..];
64 tool_name = Some(String::from_utf8_lossy(data).into_owned());
65 }
66 2 => {
67 let (v, n) = decode_varint_value(buf)?;
68 buf = &buf[n..];
69 status = Some(Status::from_wire_byte(v as u8)?);
70 }
71 3 => {
72 let (data, n) = decode_bytes_value(buf)?;
73 buf = &buf[n..];
74 content = Some(data.to_vec());
75 }
76 4 => {
77 let (data, n) = decode_bytes_value(buf)?;
78 buf = &buf[n..];
79 schema_hint = Some(String::from_utf8_lossy(data).into_owned());
80 }
81 _ => {
82 let n = skip_field(buf, header.wire_type)?;
83 buf = &buf[n..];
84 }
85 }
86 }
87
88 Ok(Self {
89 tool_name: tool_name.ok_or(TypeError::MissingRequiredField { field: "tool_name" })?,
90 status: status.ok_or(TypeError::MissingRequiredField { field: "status" })?,
91 content: content.ok_or(TypeError::MissingRequiredField { field: "content" })?,
92 schema_hint,
93 })
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn roundtrip_ok_result() {
103 let block = ToolResultBlock {
104 tool_name: "read_file".to_string(),
105 status: Status::Ok,
106 content: b"file contents here".to_vec(),
107 schema_hint: None,
108 };
109 let body = block.encode_body();
110 let decoded = ToolResultBlock::decode_body(&body).unwrap();
111 assert_eq!(decoded, block);
112 }
113
114 #[test]
115 fn roundtrip_error_with_schema() {
116 let block = ToolResultBlock {
117 tool_name: "api_call".to_string(),
118 status: Status::Error,
119 content: b"404 Not Found".to_vec(),
120 schema_hint: Some("application/json".to_string()),
121 };
122 let body = block.encode_body();
123 let decoded = ToolResultBlock::decode_body(&body).unwrap();
124 assert_eq!(decoded, block);
125 }
126
127 #[test]
128 fn roundtrip_timeout() {
129 let block = ToolResultBlock {
130 tool_name: "slow_tool".to_string(),
131 status: Status::Timeout,
132 content: b"".to_vec(),
133 schema_hint: None,
134 };
135 let body = block.encode_body();
136 let decoded = ToolResultBlock::decode_body(&body).unwrap();
137 assert_eq!(decoded, block);
138 }
139}