1use std::collections::{BTreeMap, BTreeSet};
2
3#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
4pub struct ToolCallId {
5 session: [u8; 12],
6 sequence: u64,
7}
8
9impl ToolCallId {
10 pub const fn new(session: [u8; 12], sequence: u64) -> Self {
11 Self { session, sequence }
12 }
13
14 pub const fn session(self) -> [u8; 12] {
15 self.session
16 }
17
18 pub const fn sequence(self) -> u64 {
19 self.sequence
20 }
21}
22
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub struct BoxId(u64);
25
26impl BoxId {
27 pub const fn new(value: u64) -> Self {
28 Self(value)
29 }
30
31 pub const fn get(self) -> u64 {
32 self.0
33 }
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub enum BoxContent {
38 System(String),
39 User(String),
40 Kennedy {
41 text: String,
42 },
43 Attachment,
44 KtoolCall {
45 tool_call_id: ToolCallId,
46 name: String,
47 arguments: String,
48 },
49 KtoolReturn {
50 tool_call_id: ToolCallId,
51 originating_call: BoxId,
52 result: Result<String, String>,
53 },
54}
55
56#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct ChatBox {
58 id: BoxId,
59 content: BoxContent,
60}
61
62impl ChatBox {
63 pub fn new(id: BoxId, content: BoxContent) -> Self {
64 Self { id, content }
65 }
66
67 pub const fn id(&self) -> BoxId {
68 self.id
69 }
70
71 pub const fn content(&self) -> &BoxContent {
72 &self.content
73 }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct ProviderCall {
78 pub tool_call_id: ToolCallId,
79 pub name: String,
80 pub arguments: String,
81}
82
83#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct DispatchCall {
85 pub tool_call_id: ToolCallId,
86 pub call_box_id: BoxId,
87 pub name: String,
88 pub arguments: String,
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub enum TransitionError {
93 InvalidPhase,
94 DuplicateToolCall,
95 UnknownToolCall,
96 DuplicateReturn,
97 BoxIdOverflow,
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub enum RecoveryError {
102 NonContiguousBoxId,
103 EmptyKennedy,
104 DuplicateToolCall,
105 UnknownToolCall,
106 WrongOriginatingCall,
107 DuplicateReturn,
108}
109
110#[derive(Default)]
111pub struct Chatend {
112 boxes: Vec<ChatBox>,
113 provider: bool,
114 queued: Vec<BoxContent>,
115 tool_calls: BTreeMap<ToolCallId, ToolCallRecord>,
116 last_id: u64,
117}
118
119struct ToolCallRecord {
120 call_box_id: BoxId,
121 returned: bool,
122}
123
124impl Chatend {
125 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn recover(boxes: Vec<ChatBox>) -> Result<Self, RecoveryError> {
130 let mut expected_id = 1_u64;
131 let mut tool_calls: BTreeMap<ToolCallId, ToolCallRecord> = BTreeMap::new();
132 for chat_box in &boxes {
133 if chat_box.id.get() != expected_id {
134 return Err(RecoveryError::NonContiguousBoxId);
135 }
136 match &chat_box.content {
137 BoxContent::Kennedy { text } if text.is_empty() => {
138 return Err(RecoveryError::EmptyKennedy);
139 }
140 BoxContent::KtoolCall { tool_call_id, .. } => {
141 if tool_calls.contains_key(tool_call_id) {
142 return Err(RecoveryError::DuplicateToolCall);
143 }
144 tool_calls.insert(
145 *tool_call_id,
146 ToolCallRecord {
147 call_box_id: chat_box.id,
148 returned: false,
149 },
150 );
151 }
152 BoxContent::KtoolReturn {
153 tool_call_id,
154 originating_call,
155 ..
156 } => {
157 let record = tool_calls
158 .get_mut(tool_call_id)
159 .ok_or(RecoveryError::UnknownToolCall)?;
160 if record.call_box_id != *originating_call {
161 return Err(RecoveryError::WrongOriginatingCall);
162 }
163 if record.returned {
164 return Err(RecoveryError::DuplicateReturn);
165 }
166 record.returned = true;
167 }
168 _ => {}
169 }
170 expected_id = expected_id
171 .checked_add(1)
172 .ok_or(RecoveryError::NonContiguousBoxId)?;
173 }
174 Ok(Self {
175 last_id: expected_id - 1,
176 boxes,
177 provider: false,
178 queued: Vec::new(),
179 tool_calls,
180 })
181 }
182
183 pub fn boxes(&self) -> &[ChatBox] {
184 &self.boxes
185 }
186
187 pub fn accept_system(&mut self, text: String) -> Result<Option<BoxId>, TransitionError> {
188 self.accept_arrival(BoxContent::System(text))
189 }
190
191 pub fn accept_user(&mut self, text: String) -> Result<Option<BoxId>, TransitionError> {
192 self.accept_arrival(BoxContent::User(text))
193 }
194
195 pub fn accept_attachment(&mut self) -> Result<Option<BoxId>, TransitionError> {
196 self.accept_arrival(BoxContent::Attachment)
197 }
198
199 pub fn accept_async_return(
200 &mut self,
201 tool_call_id: ToolCallId,
202 result: Result<String, String>,
203 ) -> Result<Option<BoxId>, TransitionError> {
204 let record = self
205 .tool_calls
206 .get(&tool_call_id)
207 .ok_or(TransitionError::UnknownToolCall)?;
208 if record.returned {
209 return Err(TransitionError::DuplicateReturn);
210 }
211 let originating_call = record.call_box_id;
212 if !self.provider {
213 self.ensure_capacity(1)?;
214 }
215 self.tool_calls.get_mut(&tool_call_id).unwrap().returned = true;
216 let content = BoxContent::KtoolReturn {
217 tool_call_id,
218 originating_call,
219 result,
220 };
221 if self.provider {
222 self.queued.push(content);
223 Ok(None)
224 } else {
225 Ok(Some(self.append(content)))
226 }
227 }
228
229 pub fn start_round(&mut self) -> Result<Option<BoxId>, TransitionError> {
230 if self.provider {
231 return Err(TransitionError::InvalidPhase);
232 }
233 let frontier = self.boxes.last().map(ChatBox::id);
234 self.provider = true;
235 Ok(frontier)
236 }
237
238 pub fn append_stage(
239 &mut self,
240 text: String,
241 calls: Vec<ProviderCall>,
242 ) -> Result<Vec<DispatchCall>, TransitionError> {
243 if !self.provider {
244 return Err(TransitionError::InvalidPhase);
245 }
246 let mut seen = BTreeSet::new();
247 for call in &calls {
248 if !seen.insert(call.tool_call_id) || self.tool_calls.contains_key(&call.tool_call_id) {
249 return Err(TransitionError::DuplicateToolCall);
250 }
251 }
252 let text_growth = if text.is_empty() { 0 } else { 1 };
253 let growth = calls
254 .len()
255 .checked_add(text_growth)
256 .ok_or(TransitionError::BoxIdOverflow)?;
257 self.ensure_capacity(growth)?;
258 if !text.is_empty() {
259 self.append(BoxContent::Kennedy { text });
260 }
261 let mut dispatches = Vec::with_capacity(calls.len());
262 for call in calls {
263 let call_box_id = self.append(BoxContent::KtoolCall {
264 tool_call_id: call.tool_call_id,
265 name: call.name.clone(),
266 arguments: call.arguments.clone(),
267 });
268 self.tool_calls.insert(
269 call.tool_call_id,
270 ToolCallRecord {
271 call_box_id,
272 returned: false,
273 },
274 );
275 dispatches.push(DispatchCall {
276 tool_call_id: call.tool_call_id,
277 call_box_id,
278 name: call.name,
279 arguments: call.arguments,
280 });
281 }
282 Ok(dispatches)
283 }
284
285 pub fn flush_active_arrivals(&mut self) -> Result<Vec<ChatBox>, TransitionError> {
286 if !self.provider {
287 return Err(TransitionError::InvalidPhase);
288 }
289 self.ensure_capacity(self.queued.len())?;
290 let first = self.boxes.len();
291 for content in std::mem::take(&mut self.queued) {
292 self.append(content);
293 }
294 Ok(self.boxes[first..].to_vec())
295 }
296
297 pub fn done(&mut self, text: String) -> Result<(), TransitionError> {
298 if !self.provider {
299 return Err(TransitionError::InvalidPhase);
300 }
301 let text_growth = if text.is_empty() { 0 } else { 1 };
302 let growth = self
303 .queued
304 .len()
305 .checked_add(text_growth)
306 .ok_or(TransitionError::BoxIdOverflow)?;
307 self.ensure_capacity(growth)?;
308 if !text.is_empty() {
309 self.append(BoxContent::Kennedy { text });
310 }
311 self.provider = false;
312 for content in std::mem::take(&mut self.queued) {
313 self.append(content);
314 }
315 Ok(())
316 }
317
318 fn accept_arrival(&mut self, content: BoxContent) -> Result<Option<BoxId>, TransitionError> {
319 if self.provider {
320 self.queued.push(content);
321 Ok(None)
322 } else {
323 self.ensure_capacity(1)?;
324 Ok(Some(self.append(content)))
325 }
326 }
327
328 fn ensure_capacity(&self, additional: usize) -> Result<(), TransitionError> {
329 let additional = u64::try_from(additional).map_err(|_| TransitionError::BoxIdOverflow)?;
330 self.last_id
331 .checked_add(additional)
332 .ok_or(TransitionError::BoxIdOverflow)?;
333 Ok(())
334 }
335
336 fn append(&mut self, content: BoxContent) -> BoxId {
337 self.last_id += 1;
338 let id = BoxId(self.last_id);
339 self.boxes.push(ChatBox { id, content });
340 id
341 }
342}
343
344#[cfg(test)]
345mod tests;