use kcode_k1_chat_chatend::{BoxContent, ChatBox, ToolCallId};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Call {
pub name: String,
pub arguments: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BoxValue {
History(String),
Call(Result<Call, String>),
}
#[derive(Default)]
pub struct Codec;
impl kcode_k1_codex_adapter::BoxCodec for Codec {
type Box = BoxValue;
fn tool_call_box(&mut self, call: &kcode_k1_codex_adapter::ToolCall) -> Self::Box {
if call.name != "call_ktool" {
return BoxValue::Call(Err("tool name must be call_ktool".into()));
}
let serde_json::Value::Object(values) = &call.arguments else {
return BoxValue::Call(Err("tool arguments must be an object".into()));
};
if values.len() != 2 || !values.contains_key("name") || !values.contains_key("arguments") {
return BoxValue::Call(Err("tool arguments must have name and arguments".into()));
}
let Some(serde_json::Value::String(name)) = values.get("name") else {
return BoxValue::Call(Err("tool arguments name must be a string".into()));
};
let Some(arguments) = values.get("arguments") else {
return BoxValue::Call(Err("tool arguments require arguments".into()));
};
let arguments = match serde_json::to_string(arguments) {
Ok(arguments) => arguments,
Err(_) => return BoxValue::Call(Err("tool arguments cannot be encoded".into())),
};
BoxValue::Call(Ok(Call {
name: name.clone(),
arguments,
}))
}
fn box_text<'a>(&self, box_: &'a Self::Box) -> &'a str {
match box_ {
BoxValue::History(text) => text,
BoxValue::Call(_) => "",
}
}
}
pub fn project(box_: &ChatBox) -> BoxValue {
let box_id = box_.id().get();
let text = match box_.content() {
BoxContent::System(text) => {
format!(
"{{\"box_id\":{box_id},\"kind\":\"system\",\"text\":{}}}",
json(text)
)
}
BoxContent::User(text) => {
format!(
"{{\"box_id\":{box_id},\"kind\":\"user\",\"text\":{}}}",
json(text)
)
}
BoxContent::Kennedy { text } => {
format!(
"{{\"box_id\":{box_id},\"kind\":\"kennedy\",\"text\":{}}}",
json(text)
)
}
BoxContent::Attachment => format!("{{\"box_id\":{box_id},\"kind\":\"attachment\"}}"),
BoxContent::KtoolCall {
tool_call_id,
name,
arguments,
} => format!(
"{{\"box_id\":{box_id},\"kind\":\"ktool_call\",\"tool_call_id\":{},\"name\":{},\"arguments\":{}}}",
tool_call(tool_call_id),
json(name),
json(arguments)
),
BoxContent::KtoolReturn {
tool_call_id,
originating_call,
result,
} => format!(
"{{\"box_id\":{box_id},\"kind\":\"ktool_return\",\"tool_call_id\":{},\"originating_call_box_id\":{},\"result\":{}}}",
tool_call(tool_call_id),
originating_call.get(),
result_json(result)
),
};
BoxValue::History(text)
}
fn json(text: &str) -> String {
serde_json::to_string(text).expect("strings serialize")
}
fn tool_call(tool_call_id: &ToolCallId) -> String {
let session = tool_call_id
.session()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>();
format!(
"{{\"session\":{session:?},\"sequence\":{}}}",
tool_call_id.sequence()
)
}
fn result_json(result: &Result<String, String>) -> String {
match result {
Ok(value) => format!("{{\"status\":\"ok\",\"value\":{}}}", json(value)),
Err(value) => format!("{{\"status\":\"error\",\"value\":{}}}", json(value)),
}
}