kcode-k1-chat-codex-codec 0.7.0

Native Codex wrapper validator and open-format K1 ChatBox projector
Documentation
#![forbid(unsafe_code)]

use kcode_k1_chat_chatend::{AGENT_RESPONSE_TYPE, BoxId, ChatBox};

#[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>),
    AgentMessage(Result<String, 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 {
        match call.name.as_str() {
            "call_ktool" => {
                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,
                }))
            }
            "agent_message" => {
                let serde_json::Value::Object(values) = &call.arguments else {
                    return BoxValue::AgentMessage(Err(
                        "agent_message arguments must be an object".into(),
                    ));
                };
                if values.len() != 1 || !values.contains_key("message") {
                    return BoxValue::AgentMessage(Err(
                        "agent_message arguments must have exactly one message field".into(),
                    ));
                }
                let Some(serde_json::Value::String(message)) = values.get("message") else {
                    return BoxValue::AgentMessage(Err(
                        "agent_message message must be a string".into()
                    ));
                };
                if message.is_empty() {
                    return BoxValue::AgentMessage(Err(
                        "agent_message message must not be empty".into()
                    ));
                }
                BoxValue::AgentMessage(Ok(message.clone()))
            }
            _ => BoxValue::Call(Err("tool name must be call_ktool or agent_message".into())),
        }
    }

    fn box_text<'a>(&self, box_: &'a Self::Box) -> &'a str {
        match box_ {
            BoxValue::History(text) => text,
            BoxValue::Call(_) | BoxValue::AgentMessage(_) => "",
        }
    }
}

pub fn project(box_: &ChatBox) -> BoxValue {
    BoxValue::History(format!(
        "[Box {} | {}]\n{}",
        box_.id().get(),
        box_.box_type(),
        box_.contents()
    ))
}

pub fn open_agent_response(id: BoxId) -> BoxValue {
    BoxValue::History(format!("[Box {} | {}]", id.get(), AGENT_RESPONSE_TYPE))
}