1use bcp_wire::block_frame::BlockFlags;
2
3use crate::annotation::AnnotationBlock;
4use crate::block_type::BlockType;
5use crate::code::CodeBlock;
6use crate::conversation::ConversationBlock;
7use crate::diff::DiffBlock;
8use crate::document::DocumentBlock;
9use crate::embedding_ref::EmbeddingRefBlock;
10use crate::error::TypeError;
11use crate::extension::ExtensionBlock;
12use crate::file_tree::FileTreeBlock;
13use crate::image::ImageBlock;
14use crate::structured_data::StructuredDataBlock;
15use crate::summary::Summary;
16use crate::tool_result::ToolResultBlock;
17
18#[derive(Clone, Debug, PartialEq)]
34pub struct Block {
35 pub block_type: BlockType,
36 pub flags: BlockFlags,
37 pub summary: Option<Summary>,
38 pub content: BlockContent,
39}
40
41#[derive(Clone, Debug, PartialEq)]
68pub enum BlockContent {
69 Code(CodeBlock),
70 Conversation(ConversationBlock),
71 FileTree(FileTreeBlock),
72 ToolResult(ToolResultBlock),
73 Document(DocumentBlock),
74 StructuredData(StructuredDataBlock),
75 Diff(DiffBlock),
76 Annotation(AnnotationBlock),
77 EmbeddingRef(EmbeddingRefBlock),
78 Image(ImageBlock),
79 Extension(ExtensionBlock),
80 End,
81 Unknown {
83 type_id: u8,
84 body: Vec<u8>,
85 },
86}
87
88impl BlockContent {
89 pub fn encode_body(&self) -> Vec<u8> {
94 match self {
95 Self::Code(b) => b.encode_body(),
96 Self::Conversation(b) => b.encode_body(),
97 Self::FileTree(b) => b.encode_body(),
98 Self::ToolResult(b) => b.encode_body(),
99 Self::Document(b) => b.encode_body(),
100 Self::StructuredData(b) => b.encode_body(),
101 Self::Diff(b) => b.encode_body(),
102 Self::Annotation(b) => b.encode_body(),
103 Self::EmbeddingRef(b) => b.encode_body(),
104 Self::Image(b) => b.encode_body(),
105 Self::Extension(b) => b.encode_body(),
106 Self::End => Vec::new(),
107 Self::Unknown { body, .. } => body.clone(),
108 }
109 }
110
111 pub fn decode_body(block_type: &BlockType, body: &[u8]) -> Result<Self, TypeError> {
116 match block_type {
117 BlockType::Code => Ok(Self::Code(CodeBlock::decode_body(body)?)),
118 BlockType::Conversation => {
119 Ok(Self::Conversation(ConversationBlock::decode_body(body)?))
120 }
121 BlockType::FileTree => Ok(Self::FileTree(FileTreeBlock::decode_body(body)?)),
122 BlockType::ToolResult => Ok(Self::ToolResult(ToolResultBlock::decode_body(body)?)),
123 BlockType::Document => Ok(Self::Document(DocumentBlock::decode_body(body)?)),
124 BlockType::StructuredData => Ok(Self::StructuredData(
125 StructuredDataBlock::decode_body(body)?,
126 )),
127 BlockType::Diff => Ok(Self::Diff(DiffBlock::decode_body(body)?)),
128 BlockType::Annotation => Ok(Self::Annotation(AnnotationBlock::decode_body(body)?)),
129 BlockType::EmbeddingRef => {
130 Ok(Self::EmbeddingRef(EmbeddingRefBlock::decode_body(body)?))
131 }
132 BlockType::Image => Ok(Self::Image(ImageBlock::decode_body(body)?)),
133 BlockType::Extension => Ok(Self::Extension(ExtensionBlock::decode_body(body)?)),
134 BlockType::End => Ok(Self::End),
135 BlockType::Unknown(id) => Ok(Self::Unknown {
136 type_id: *id,
137 body: body.to_vec(),
138 }),
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::enums::{FormatHint, Lang, Role, Status};
147
148 #[test]
149 fn block_encode_decode_roundtrip() {
150 let block = Block {
151 block_type: BlockType::Code,
152 flags: BlockFlags::NONE,
153 summary: None,
154 content: BlockContent::Code(CodeBlock {
155 lang: Lang::Rust,
156 path: "lib.rs".to_string(),
157 content: b"pub fn hello() {}".to_vec(),
158 line_range: None,
159 }),
160 };
161
162 let body = block.content.encode_body();
163 let decoded = BlockContent::decode_body(&block.block_type, &body).unwrap();
164 assert_eq!(decoded, block.content);
165 }
166
167 #[test]
168 fn block_with_summary() {
169 let summary = Summary {
170 text: "Main entry point".to_string(),
171 };
172 let content = BlockContent::Code(CodeBlock {
173 lang: Lang::Rust,
174 path: "main.rs".to_string(),
175 content: b"fn main() {}".to_vec(),
176 line_range: None,
177 });
178
179 let mut body = Vec::new();
181 summary.encode(&mut body);
182 body.extend_from_slice(&content.encode_body());
183
184 let (decoded_summary, consumed) = Summary::decode(&body).unwrap();
186 let decoded_content =
187 BlockContent::decode_body(&BlockType::Code, &body[consumed..]).unwrap();
188
189 assert_eq!(decoded_summary, summary);
190 assert_eq!(decoded_content, content);
191 }
192
193 #[test]
194 fn unknown_block_type_preserved() {
195 let raw_body = b"arbitrary bytes".to_vec();
196 let block_type = BlockType::Unknown(0x42);
197
198 let content = BlockContent::decode_body(&block_type, &raw_body).unwrap();
199 assert_eq!(
200 content,
201 BlockContent::Unknown {
202 type_id: 0x42,
203 body: raw_body.clone()
204 }
205 );
206
207 assert_eq!(content.encode_body(), raw_body);
209 }
210
211 #[test]
212 fn end_block_empty_body() {
213 let content = BlockContent::decode_body(&BlockType::End, &[]).unwrap();
214 assert_eq!(content, BlockContent::End);
215 assert!(content.encode_body().is_empty());
216 }
217
218 #[test]
219 fn all_block_types_dispatch() {
220 let code = CodeBlock {
224 lang: Lang::Python,
225 path: "x.py".to_string(),
226 content: b"pass".to_vec(),
227 line_range: None,
228 };
229 let body = code.encode_body();
230 let result = BlockContent::decode_body(&BlockType::Code, &body).unwrap();
231 assert!(matches!(result, BlockContent::Code(_)));
232
233 let conv = ConversationBlock {
234 role: Role::User,
235 content: b"hi".to_vec(),
236 tool_call_id: None,
237 };
238 let body = conv.encode_body();
239 let result = BlockContent::decode_body(&BlockType::Conversation, &body).unwrap();
240 assert!(matches!(result, BlockContent::Conversation(_)));
241
242 let doc = DocumentBlock {
243 title: "t".to_string(),
244 content: b"c".to_vec(),
245 format_hint: FormatHint::Plain,
246 };
247 let body = doc.encode_body();
248 let result = BlockContent::decode_body(&BlockType::Document, &body).unwrap();
249 assert!(matches!(result, BlockContent::Document(_)));
250
251 let tool = ToolResultBlock {
252 tool_name: "t".to_string(),
253 status: Status::Ok,
254 content: b"ok".to_vec(),
255 schema_hint: None,
256 };
257 let body = tool.encode_body();
258 let result = BlockContent::decode_body(&BlockType::ToolResult, &body).unwrap();
259 assert!(matches!(result, BlockContent::ToolResult(_)));
260 }
261}