Skip to main content

kcode_k1_chat_chatend/
lib.rs

1#![forbid(unsafe_code)]
2
3pub const SYSTEM_MESSAGE_TYPE: &str = "System Message";
4pub const USER_MESSAGE_TYPE: &str = "User Message";
5pub const AGENT_MESSAGE_TYPE: &str = "Agent Message";
6pub const ATTACHMENT_TYPE: &str = "Attachment";
7pub const TOOL_CALL_TYPE: &str = "Tool Call";
8pub const TOOL_RESULT_TYPE: &str = "Tool Result";
9
10pub const TOOL_CALL_HIDDEN_TYPE: &str = "k1.tool-call/v1";
11pub const TOOL_RESULT_HIDDEN_TYPE: &str = "k1.tool-result/v1";
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct BoxId(u64);
15
16impl BoxId {
17    pub const fn new(value: u64) -> Self {
18        Self(value)
19    }
20
21    pub const fn get(self) -> u64 {
22        self.0
23    }
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct ChatBox {
28    id: BoxId,
29    box_type: String,
30    contents: String,
31    hidden_type: String,
32    hidden_contents: String,
33}
34
35impl ChatBox {
36    pub fn new(
37        id: BoxId,
38        box_type: String,
39        contents: String,
40        hidden_type: String,
41        hidden_contents: String,
42    ) -> Self {
43        Self {
44            id,
45            box_type,
46            contents,
47            hidden_type,
48            hidden_contents,
49        }
50    }
51
52    pub const fn id(&self) -> BoxId {
53        self.id
54    }
55
56    pub fn box_type(&self) -> &str {
57        &self.box_type
58    }
59
60    pub fn contents(&self) -> &str {
61        &self.contents
62    }
63
64    pub fn hidden_type(&self) -> &str {
65        &self.hidden_type
66    }
67
68    pub fn hidden_contents(&self) -> &str {
69        &self.hidden_contents
70    }
71
72    pub fn tool_call_metadata(&self) -> Result<Option<ProviderCall>, RecoveryError> {
73        if self.box_type != TOOL_CALL_TYPE || self.hidden_type != TOOL_CALL_HIDDEN_TYPE {
74            return Ok(None);
75        }
76
77        let fields = decode_fields(&self.hidden_contents, 4)
78            .ok_or(RecoveryError::MalformedToolConvention)?;
79        let nonce = decode_nonce(fields[0]).ok_or(RecoveryError::MalformedToolConvention)?;
80        let sequence = fields[1]
81            .parse::<u64>()
82            .map_err(|_| RecoveryError::MalformedToolConvention)?;
83
84        Ok(Some(ProviderCall {
85            tool_call_id: ToolCallId::new(nonce, sequence),
86            name: fields[2].to_owned(),
87            arguments: fields[3].to_owned(),
88        }))
89    }
90
91    pub fn tool_result_metadata(&self) -> Result<Option<ToolResultMetadata>, RecoveryError> {
92        if self.box_type != TOOL_RESULT_TYPE || self.hidden_type != TOOL_RESULT_HIDDEN_TYPE {
93            return Ok(None);
94        }
95
96        let fields = decode_fields(&self.hidden_contents, 5)
97            .ok_or(RecoveryError::MalformedToolConvention)?;
98        let nonce = decode_nonce(fields[0]).ok_or(RecoveryError::MalformedToolConvention)?;
99        let sequence = fields[1]
100            .parse::<u64>()
101            .map_err(|_| RecoveryError::MalformedToolConvention)?;
102        let originating_call = fields[2]
103            .parse::<u64>()
104            .map_err(|_| RecoveryError::MalformedToolConvention)?;
105        let result = match fields[3] {
106            "ok" => Ok(fields[4].to_owned()),
107            "err" => Err(fields[4].to_owned()),
108            _ => return Err(RecoveryError::MalformedToolConvention),
109        };
110
111        Ok(Some(ToolResultMetadata {
112            tool_call_id: ToolCallId::new(nonce, sequence),
113            originating_call: BoxId::new(originating_call),
114            result,
115        }))
116    }
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120pub struct ToolCallId {
121    nonce: [u8; 12],
122    sequence: u64,
123}
124
125impl ToolCallId {
126    pub const fn new(nonce: [u8; 12], sequence: u64) -> Self {
127        Self { nonce, sequence }
128    }
129
130    pub const fn nonce(self) -> [u8; 12] {
131        self.nonce
132    }
133
134    pub const fn sequence(self) -> u64 {
135        self.sequence
136    }
137}
138
139#[derive(Clone, Debug, Eq, PartialEq)]
140pub struct ProviderCall {
141    pub tool_call_id: ToolCallId,
142    pub name: String,
143    pub arguments: String,
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
147pub struct DispatchedToolCall {
148    pub tool_call_id: ToolCallId,
149    pub call_box_id: BoxId,
150}
151
152#[derive(Clone, Debug, Eq, PartialEq)]
153pub struct ToolResultMetadata {
154    pub tool_call_id: ToolCallId,
155    pub originating_call: BoxId,
156    pub result: Result<String, String>,
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum TransitionError {
161    InvalidPhase,
162    BoxIdOverflow,
163    DuplicateToolCall,
164    UnknownToolCall,
165    DuplicateReturn,
166    MalformedToolConvention,
167    WrongOriginatingCall,
168}
169
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub enum RecoveryError {
172    NonContiguousBoxId,
173    MalformedToolConvention,
174    DuplicateToolCall,
175    UnknownToolCall,
176    DuplicateReturn,
177    WrongOriginatingCall,
178}
179
180pub fn tool_call_box(call: &ProviderCall) -> ChatBox {
181    let nonce = encode_nonce(call.tool_call_id.nonce);
182    let sequence = call.tool_call_id.sequence.to_string();
183    let hidden_contents = encode_fields(&[&nonce, &sequence, &call.name, &call.arguments]);
184
185    ChatBox::new(
186        BoxId::new(0),
187        TOOL_CALL_TYPE.to_owned(),
188        format!("Tool: {}\nArguments:\n{}", call.name, call.arguments),
189        TOOL_CALL_HIDDEN_TYPE.to_owned(),
190        hidden_contents,
191    )
192}
193
194pub fn tool_result_box(
195    tool_call_id: ToolCallId,
196    originating_call: BoxId,
197    result: Result<String, String>,
198) -> ChatBox {
199    let nonce = encode_nonce(tool_call_id.nonce);
200    let sequence = tool_call_id.sequence.to_string();
201    let originating_call = originating_call.get().to_string();
202    let (status, contents) = match &result {
203        Ok(contents) => ("ok", contents.clone()),
204        Err(contents) => ("err", contents.clone()),
205    };
206    let hidden_contents = encode_fields(&[&nonce, &sequence, &originating_call, status, &contents]);
207
208    ChatBox::new(
209        BoxId::new(0),
210        TOOL_RESULT_TYPE.to_owned(),
211        contents,
212        TOOL_RESULT_HIDDEN_TYPE.to_owned(),
213        hidden_contents,
214    )
215}
216
217pub struct Chatend {
218    boxes: Vec<ChatBox>,
219    round_active: bool,
220    active_arrivals: Vec<ChatBox>,
221}
222
223impl Chatend {
224    pub const fn new() -> Self {
225        Self {
226            boxes: Vec::new(),
227            round_active: false,
228            active_arrivals: Vec::new(),
229        }
230    }
231
232    pub fn boxes(&self) -> &[ChatBox] {
233        &self.boxes
234    }
235
236    pub const fn round_active(&self) -> bool {
237        self.round_active
238    }
239
240    pub fn accept_box(
241        &mut self,
242        box_type: String,
243        contents: String,
244        hidden_type: String,
245        hidden_contents: String,
246    ) -> Result<Option<BoxId>, TransitionError> {
247        self.accept_arrival(ChatBox::new(
248            BoxId::new(0),
249            box_type,
250            contents,
251            hidden_type,
252            hidden_contents,
253        ))
254    }
255
256    pub fn accept_system(&mut self, contents: String) -> Result<Option<BoxId>, TransitionError> {
257        self.accept_box(
258            SYSTEM_MESSAGE_TYPE.to_owned(),
259            contents,
260            String::new(),
261            String::new(),
262        )
263    }
264
265    pub fn accept_user(&mut self, contents: String) -> Result<Option<BoxId>, TransitionError> {
266        self.accept_box(
267            USER_MESSAGE_TYPE.to_owned(),
268            contents,
269            String::new(),
270            String::new(),
271        )
272    }
273
274    pub fn accept_attachment(
275        &mut self,
276        contents: String,
277        hidden_type: String,
278        hidden_contents: String,
279    ) -> Result<Option<BoxId>, TransitionError> {
280        self.accept_box(
281            ATTACHMENT_TYPE.to_owned(),
282            contents,
283            hidden_type,
284            hidden_contents,
285        )
286    }
287
288    pub fn start_round(&mut self) -> Result<Option<BoxId>, TransitionError> {
289        if self.round_active {
290            return Err(TransitionError::InvalidPhase);
291        }
292
293        self.round_active = true;
294        Ok(self.boxes.last().map(ChatBox::id))
295    }
296
297    pub fn append_stage(
298        &mut self,
299        agent_contents: String,
300        calls: Vec<ProviderCall>,
301    ) -> Result<Vec<DispatchedToolCall>, TransitionError> {
302        if !self.round_active {
303            return Err(TransitionError::InvalidPhase);
304        }
305
306        for (index, call) in calls.iter().enumerate() {
307            if self.call_box_id(call.tool_call_id)?.is_some()
308                || calls[..index]
309                    .iter()
310                    .any(|earlier| earlier.tool_call_id == call.tool_call_id)
311            {
312                return Err(TransitionError::DuplicateToolCall);
313            }
314        }
315
316        let has_agent_contents = !agent_contents.is_empty();
317        let mut additions = Vec::with_capacity(calls.len() + usize::from(has_agent_contents));
318        if has_agent_contents {
319            additions.push(ChatBox::new(
320                BoxId::new(0),
321                AGENT_MESSAGE_TYPE.to_owned(),
322                agent_contents,
323                String::new(),
324                String::new(),
325            ));
326        }
327        additions.extend(calls.iter().map(tool_call_box));
328
329        let appended = self.append_batch(additions)?;
330        let call_offset = usize::from(has_agent_contents);
331        Ok(calls
332            .iter()
333            .zip(appended[call_offset..].iter())
334            .map(|(call, value)| DispatchedToolCall {
335                tool_call_id: call.tool_call_id,
336                call_box_id: value.id(),
337            })
338            .collect())
339    }
340
341    pub fn accept_async_return(
342        &mut self,
343        tool_call_id: ToolCallId,
344        result: Result<String, String>,
345    ) -> Result<Option<BoxId>, TransitionError> {
346        let originating_call = self
347            .call_box_id(tool_call_id)?
348            .ok_or(TransitionError::UnknownToolCall)?;
349        if self.has_return(tool_call_id)? {
350            return Err(TransitionError::DuplicateReturn);
351        }
352
353        self.accept_arrival(tool_result_box(tool_call_id, originating_call, result))
354    }
355
356    pub fn flush_active_arrivals(&mut self) -> Result<Vec<ChatBox>, TransitionError> {
357        if !self.round_active {
358            return Err(TransitionError::InvalidPhase);
359        }
360
361        let appended = self.append_batch(self.active_arrivals.clone())?;
362        self.active_arrivals.clear();
363        Ok(appended)
364    }
365
366    pub fn done(&mut self, final_agent_contents: String) -> Result<Vec<ChatBox>, TransitionError> {
367        if !self.round_active {
368            return Err(TransitionError::InvalidPhase);
369        }
370
371        let has_final_contents = !final_agent_contents.is_empty();
372        let mut additions =
373            Vec::with_capacity(self.active_arrivals.len() + usize::from(has_final_contents));
374        if has_final_contents {
375            additions.push(ChatBox::new(
376                BoxId::new(0),
377                AGENT_MESSAGE_TYPE.to_owned(),
378                final_agent_contents,
379                String::new(),
380                String::new(),
381            ));
382        }
383        additions.extend(self.active_arrivals.iter().cloned());
384
385        let appended = self.append_batch(additions)?;
386        self.active_arrivals.clear();
387        self.round_active = false;
388        Ok(appended)
389    }
390
391    pub fn recover(boxes: Vec<ChatBox>) -> Result<Self, RecoveryError> {
392        let mut calls = Vec::<(ToolCallId, BoxId)>::new();
393        let mut returned = Vec::<ToolCallId>::new();
394
395        for (index, value) in boxes.iter().enumerate() {
396            let expected = u64::try_from(index)
397                .ok()
398                .and_then(|index| index.checked_add(1))
399                .ok_or(RecoveryError::NonContiguousBoxId)?;
400            if value.id().get() != expected {
401                return Err(RecoveryError::NonContiguousBoxId);
402            }
403
404            if let Some(call) = value.tool_call_metadata()? {
405                if calls
406                    .iter()
407                    .any(|(tool_call_id, _)| *tool_call_id == call.tool_call_id)
408                {
409                    return Err(RecoveryError::DuplicateToolCall);
410                }
411                calls.push((call.tool_call_id, value.id()));
412            }
413
414            if let Some(result) = value.tool_result_metadata()? {
415                let Some((_, call_box_id)) = calls
416                    .iter()
417                    .find(|(tool_call_id, _)| *tool_call_id == result.tool_call_id)
418                else {
419                    return Err(RecoveryError::UnknownToolCall);
420                };
421                if *call_box_id != result.originating_call {
422                    return Err(RecoveryError::WrongOriginatingCall);
423                }
424                if returned.contains(&result.tool_call_id) {
425                    return Err(RecoveryError::DuplicateReturn);
426                }
427                returned.push(result.tool_call_id);
428            }
429        }
430
431        Ok(Self {
432            boxes,
433            round_active: false,
434            active_arrivals: Vec::new(),
435        })
436    }
437
438    fn accept_arrival(&mut self, value: ChatBox) -> Result<Option<BoxId>, TransitionError> {
439        if self.round_active {
440            self.active_arrivals.push(value);
441            Ok(None)
442        } else {
443            let mut appended = self.append_batch(vec![value])?;
444            Ok(appended.pop().map(|value| value.id()))
445        }
446    }
447
448    fn append_batch(
449        &mut self,
450        mut additions: Vec<ChatBox>,
451    ) -> Result<Vec<ChatBox>, TransitionError> {
452        self.ensure_capacity(additions.len())?;
453
454        let mut previous = self.boxes.last().map_or(0, |value| value.id().get());
455        for value in &mut additions {
456            previous = previous
457                .checked_add(1)
458                .ok_or(TransitionError::BoxIdOverflow)?;
459            value.id = BoxId::new(previous);
460        }
461
462        self.boxes.extend(additions.iter().cloned());
463        Ok(additions)
464    }
465
466    fn ensure_capacity(&self, additional: usize) -> Result<(), TransitionError> {
467        let additional = u64::try_from(additional).map_err(|_| TransitionError::BoxIdOverflow)?;
468        let previous = self.boxes.last().map_or(0, |value| value.id().get());
469        previous
470            .checked_add(additional)
471            .ok_or(TransitionError::BoxIdOverflow)?;
472        Ok(())
473    }
474
475    fn call_box_id(&self, tool_call_id: ToolCallId) -> Result<Option<BoxId>, TransitionError> {
476        let mut found = None;
477        for value in &self.boxes {
478            let metadata = value
479                .tool_call_metadata()
480                .map_err(|_| TransitionError::MalformedToolConvention)?;
481            if metadata
482                .as_ref()
483                .is_some_and(|call| call.tool_call_id == tool_call_id)
484            {
485                if found.is_some() {
486                    return Err(TransitionError::DuplicateToolCall);
487                }
488                found = Some(value.id());
489            }
490        }
491        Ok(found)
492    }
493
494    fn has_return(&self, tool_call_id: ToolCallId) -> Result<bool, TransitionError> {
495        for value in self.boxes.iter().chain(&self.active_arrivals) {
496            let metadata = value
497                .tool_result_metadata()
498                .map_err(|_| TransitionError::MalformedToolConvention)?;
499            if metadata
500                .as_ref()
501                .is_some_and(|result| result.tool_call_id == tool_call_id)
502            {
503                return Ok(true);
504            }
505        }
506        Ok(false)
507    }
508}
509
510impl Default for Chatend {
511    fn default() -> Self {
512        Self::new()
513    }
514}
515
516fn encode_fields(fields: &[&str]) -> String {
517    let mut encoded = String::new();
518    for field in fields {
519        encoded.push_str(&field.len().to_string());
520        encoded.push(':');
521        encoded.push_str(field);
522    }
523    encoded
524}
525
526fn decode_fields(input: &str, count: usize) -> Option<Vec<&str>> {
527    let mut fields = Vec::with_capacity(count);
528    let mut cursor = 0;
529
530    for _ in 0..count {
531        let colon_offset = input
532            .as_bytes()
533            .get(cursor..)?
534            .iter()
535            .position(|byte| *byte == b':')?;
536        let colon = cursor.checked_add(colon_offset)?;
537        let length = input.get(cursor..colon)?.parse::<usize>().ok()?;
538        let start = colon.checked_add(1)?;
539        let end = start.checked_add(length)?;
540        fields.push(input.get(start..end)?);
541        cursor = end;
542    }
543
544    (cursor == input.len()).then_some(fields)
545}
546
547fn encode_nonce(nonce: [u8; 12]) -> String {
548    const HEX: &[u8; 16] = b"0123456789abcdef";
549    let mut encoded = String::with_capacity(24);
550    for byte in nonce {
551        encoded.push(char::from(HEX[usize::from(byte >> 4)]));
552        encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
553    }
554    encoded
555}
556
557fn decode_nonce(value: &str) -> Option<[u8; 12]> {
558    if value.len() != 24 {
559        return None;
560    }
561
562    let mut nonce = [0; 12];
563    for (index, slot) in nonce.iter_mut().enumerate() {
564        let offset = index.checked_mul(2)?;
565        let high = decode_hex(*value.as_bytes().get(offset)?)?;
566        let low = decode_hex(*value.as_bytes().get(offset.checked_add(1)?)?)?;
567        *slot = (high << 4) | low;
568    }
569    Some(nonce)
570}
571
572const fn decode_hex(value: u8) -> Option<u8> {
573    match value {
574        b'0'..=b'9' => Some(value - b'0'),
575        b'a'..=b'f' => Some(value - b'a' + 10),
576        b'A'..=b'F' => Some(value - b'A' + 10),
577        _ => None,
578    }
579}
580
581#[cfg(test)]
582mod tests;