Skip to main content

kcode_k1_chat_codex_codec/
lib.rs

1use kcode_k1_chat_chatend::{BoxContent, ChatBox, ToolCallId};
2
3#[derive(Clone, Debug, Eq, PartialEq)]
4pub struct Call {
5    pub name: String,
6    pub arguments: String,
7}
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub enum BoxValue {
11    History(String),
12    Call(Result<Call, String>),
13}
14
15#[derive(Default)]
16pub struct Codec;
17
18impl kcode_k1_codex_adapter::BoxCodec for Codec {
19    type Box = BoxValue;
20
21    fn tool_call_box(&mut self, call: &kcode_k1_codex_adapter::ToolCall) -> Self::Box {
22        if call.name != "call_ktool" {
23            return BoxValue::Call(Err("tool name must be call_ktool".into()));
24        }
25        let serde_json::Value::Object(values) = &call.arguments else {
26            return BoxValue::Call(Err("tool arguments must be an object".into()));
27        };
28        if values.len() != 2 || !values.contains_key("name") || !values.contains_key("arguments") {
29            return BoxValue::Call(Err("tool arguments must have name and arguments".into()));
30        }
31        let Some(serde_json::Value::String(name)) = values.get("name") else {
32            return BoxValue::Call(Err("tool arguments name must be a string".into()));
33        };
34        let Some(arguments) = values.get("arguments") else {
35            return BoxValue::Call(Err("tool arguments require arguments".into()));
36        };
37        let arguments = match serde_json::to_string(arguments) {
38            Ok(arguments) => arguments,
39            Err(_) => return BoxValue::Call(Err("tool arguments cannot be encoded".into())),
40        };
41        BoxValue::Call(Ok(Call {
42            name: name.clone(),
43            arguments,
44        }))
45    }
46
47    fn box_text<'a>(&self, box_: &'a Self::Box) -> &'a str {
48        match box_ {
49            BoxValue::History(text) => text,
50            BoxValue::Call(_) => "",
51        }
52    }
53}
54
55pub fn project(box_: &ChatBox) -> BoxValue {
56    let box_id = box_.id().get();
57    let text = match box_.content() {
58        BoxContent::System(text) => {
59            format!(
60                "{{\"box_id\":{box_id},\"kind\":\"system\",\"text\":{}}}",
61                json(text)
62            )
63        }
64        BoxContent::User(text) => {
65            format!(
66                "{{\"box_id\":{box_id},\"kind\":\"user\",\"text\":{}}}",
67                json(text)
68            )
69        }
70        BoxContent::Kennedy { text } => {
71            format!(
72                "{{\"box_id\":{box_id},\"kind\":\"kennedy\",\"text\":{}}}",
73                json(text)
74            )
75        }
76        BoxContent::Attachment => format!("{{\"box_id\":{box_id},\"kind\":\"attachment\"}}"),
77        BoxContent::KtoolCall {
78            tool_call_id,
79            name,
80            arguments,
81        } => format!(
82            "{{\"box_id\":{box_id},\"kind\":\"ktool_call\",\"tool_call_id\":{},\"name\":{},\"arguments\":{}}}",
83            tool_call(tool_call_id),
84            json(name),
85            json(arguments)
86        ),
87        BoxContent::KtoolReturn {
88            tool_call_id,
89            originating_call,
90            result,
91        } => format!(
92            "{{\"box_id\":{box_id},\"kind\":\"ktool_return\",\"tool_call_id\":{},\"originating_call_box_id\":{},\"result\":{}}}",
93            tool_call(tool_call_id),
94            originating_call.get(),
95            result_json(result)
96        ),
97    };
98    BoxValue::History(text)
99}
100
101fn json(text: &str) -> String {
102    serde_json::to_string(text).expect("strings serialize")
103}
104
105fn tool_call(tool_call_id: &ToolCallId) -> String {
106    let session = tool_call_id
107        .session()
108        .iter()
109        .map(|byte| format!("{byte:02x}"))
110        .collect::<String>();
111    format!(
112        "{{\"session\":{session:?},\"sequence\":{}}}",
113        tool_call_id.sequence()
114    )
115}
116
117fn result_json(result: &Result<String, String>) -> String {
118    match result {
119        Ok(value) => format!("{{\"status\":\"ok\",\"value\":{}}}", json(value)),
120        Err(value) => format!("{{\"status\":\"error\",\"value\":{}}}", json(value)),
121    }
122}