kcode_k1_chat_codex_codec/
lib.rs1use kcode_k1_chat_chatend::{BoxContent, ChatBox};
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) => format!(
59 "{{\"box_id\":{box_id},\"kind\":\"system\",\"text\":{}}}",
60 json(text)
61 ),
62 BoxContent::User(text) => format!(
63 "{{\"box_id\":{box_id},\"kind\":\"user\",\"text\":{}}}",
64 json(text)
65 ),
66 BoxContent::Kennedy { text, complete } => format!(
67 "{{\"box_id\":{box_id},\"kind\":\"kennedy\",\"text\":{},\"complete\":{complete}}}",
68 json(text)
69 ),
70 BoxContent::Attachment => format!("{{\"box_id\":{box_id},\"kind\":\"attachment\"}}"),
71 BoxContent::KtoolCall {
72 action_id,
73 name,
74 arguments,
75 } => format!(
76 "{{\"box_id\":{box_id},\"kind\":\"ktool_call\",\"action_id\":{},\"name\":{},\"arguments\":{}}}",
77 action(action_id),
78 json(name),
79 json(arguments)
80 ),
81 BoxContent::KtoolReturn {
82 action_id,
83 originating_call,
84 result,
85 } => format!(
86 "{{\"box_id\":{box_id},\"kind\":\"ktool_return\",\"action_id\":{},\"originating_call_box_id\":{},\"result\":{}}}",
87 action(action_id),
88 originating_call.get(),
89 result_json(result)
90 ),
91 };
92 BoxValue::History(text)
93}
94
95fn json(text: &str) -> String {
96 serde_json::to_string(text).expect("strings serialize")
97}
98
99fn action(action_id: &kcode_k1_chat_chatend::ActionId) -> String {
100 let session = action_id
101 .session()
102 .iter()
103 .map(|byte| format!("{byte:02x}"))
104 .collect::<String>();
105 format!(
106 "{{\"session\":{session:?},\"sequence\":{}}}",
107 action_id.sequence()
108 )
109}
110
111fn result_json(result: &Result<String, String>) -> String {
112 match result {
113 Ok(value) => format!("{{\"status\":\"ok\",\"value\":{}}}", json(value)),
114 Err(value) => format!("{{\"status\":\"error\",\"value\":{}}}", json(value)),
115 }
116}