kcode-k1-chat-web-code-output 0.2.0

Pure model-facing outputs for K1 Chat WebCode tasks
Documentation
#![forbid(unsafe_code)]

use kcode_k1_chat_boxes::ToolCallId;
pub use kcode_k1_web_code_ktool_protocol::{
    Language, WEB_CODE_CHECK_METADATA_TYPE, WEB_CODE_CREATE_METADATA_TYPE,
    WEB_CODE_DOCS_METADATA_TYPE, WEB_CODE_OPEN_METADATA_TYPE, WEB_CODE_OVERWRITE_METADATA_TYPE,
    WEB_CODE_PUBLISH_METADATA_TYPE,
};
use serde::Serialize;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WebCodeOutput {
    pub tool_call_id: ToolCallId,
    pub messages: Vec<String>,
    pub result: Result<String, String>,
    pub metadata_type: String,
    pub metadata_contents: String,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublishStatus {
    Published,
    Idempotent,
    Conflict,
}
impl PublishStatus {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Published => "published",
            Self::Idempotent => "idempotent",
            Self::Conflict => "conflict",
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DocumentPart {
    Documentation,
    Code,
}
impl DocumentPart {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Documentation => "documentation",
            Self::Code => "code",
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Operation {
    Create,
    Docs,
    Open,
    Overwrite,
    Check,
    Publish,
}
impl Operation {
    pub const fn metadata_type(self) -> &'static str {
        match self {
            Self::Create => WEB_CODE_CREATE_METADATA_TYPE,
            Self::Docs => WEB_CODE_DOCS_METADATA_TYPE,
            Self::Open => WEB_CODE_OPEN_METADATA_TYPE,
            Self::Overwrite => WEB_CODE_OVERWRITE_METADATA_TYPE,
            Self::Check => WEB_CODE_CHECK_METADATA_TYPE,
            Self::Publish => WEB_CODE_PUBLISH_METADATA_TYPE,
        }
    }
}

#[derive(Serialize)]
struct DocsMetadata<'a> {
    status: &'static str,
    name: &'a str,
    version: &'a str,
}
#[derive(Serialize)]
struct SourceMetadata<'a> {
    status: &'static str,
    name: &'a str,
    version: &'a str,
    language: &'static str,
    documentation_message_index: u64,
    code_message_start: u64,
    code_message_count: u64,
}
#[derive(Serialize)]
struct OverwriteMetadata<'a> {
    status: &'static str,
    box_id: u64,
    part: &'static str,
    version: &'a str,
}
#[derive(Serialize)]
struct CheckMetadata<'a> {
    status: &'static str,
    fresh: bool,
    diagnostics: Option<&'a str>,
}
#[derive(Serialize)]
struct PublishMetadata<'a> {
    status: &'static str,
    version: &'a str,
    source_object: &'a str,
    winning: &'a str,
    submitted: Option<&'a str>,
    check: &'static str,
}
#[derive(Serialize)]
struct ErrorMetadata<'a> {
    status: &'static str,
    code: &'a str,
    source_object: Option<&'a str>,
}

pub fn docs(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    name: &str,
    version: &str,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        WEB_CODE_DOCS_METADATA_TYPE,
        &DocsMetadata {
            status: "found",
            name,
            version,
        },
    )
}

#[allow(clippy::too_many_arguments)]
pub fn create(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    name: &str,
    version: &str,
    language: Language,
    code_message_count: u64,
) -> WebCodeOutput {
    source(
        tool_call_id,
        messages,
        result,
        WEB_CODE_CREATE_METADATA_TYPE,
        "created",
        name,
        version,
        language,
        code_message_count,
    )
}

#[allow(clippy::too_many_arguments)]
pub fn open(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    name: &str,
    version: &str,
    language: Language,
    code_message_count: u64,
) -> WebCodeOutput {
    source(
        tool_call_id,
        messages,
        result,
        WEB_CODE_OPEN_METADATA_TYPE,
        "opened",
        name,
        version,
        language,
        code_message_count,
    )
}

#[allow(clippy::too_many_arguments)]
fn source(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    metadata_type: &str,
    status: &'static str,
    name: &str,
    version: &str,
    language: Language,
    code_message_count: u64,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        metadata_type,
        &SourceMetadata {
            status,
            name,
            version,
            language: language.as_str(),
            documentation_message_index: 1,
            code_message_start: 2,
            code_message_count,
        },
    )
}

pub fn overwrite(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    box_id: u64,
    part: DocumentPart,
    version: &str,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        WEB_CODE_OVERWRITE_METADATA_TYPE,
        &OverwriteMetadata {
            status: "overwritten",
            box_id,
            part: part.as_str(),
            version,
        },
    )
}

pub fn check_success(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        WEB_CODE_CHECK_METADATA_TYPE,
        &CheckMetadata {
            status: "success",
            fresh: true,
            diagnostics: None,
        },
    )
}

pub fn check_failed(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    diagnostics: &str,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        WEB_CODE_CHECK_METADATA_TYPE,
        &CheckMetadata {
            status: "failed",
            fresh: true,
            diagnostics: Some(diagnostics),
        },
    )
}

#[allow(clippy::too_many_arguments)]
pub fn publish(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    version: &str,
    source_object: &str,
    winning: &str,
    submitted: Option<&str>,
    status: PublishStatus,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        WEB_CODE_PUBLISH_METADATA_TYPE,
        &PublishMetadata {
            status: status.as_str(),
            version,
            source_object,
            winning,
            submitted,
            check: "fresh",
        },
    )
}

pub fn error(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    operation: Operation,
    code: &str,
    source_object: Option<&str>,
) -> WebCodeOutput {
    build(
        tool_call_id,
        messages,
        result,
        operation.metadata_type(),
        &ErrorMetadata {
            status: "error",
            code,
            source_object,
        },
    )
}

fn build(
    tool_call_id: ToolCallId,
    messages: Vec<String>,
    result: Result<String, String>,
    metadata_type: &str,
    metadata: &impl Serialize,
) -> WebCodeOutput {
    WebCodeOutput {
        tool_call_id,
        messages,
        result,
        metadata_type: metadata_type.to_owned(),
        metadata_contents: serde_json::to_string(metadata)
            .expect("fixed WebCode metadata serializes"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    fn id() -> ToolCallId {
        ToolCallId::new([1; 12], 7)
    }
    #[test]
    fn metadata_is_exact_and_authority_free() {
        let value = create(
            id(),
            vec!["docs".into(), "code".into()],
            Ok("Created 010101010101010101010101.".into()),
            "alpha",
            "010101010101010101010101",
            Language::JavaScript,
            1,
        );
        assert_eq!(value.metadata_type, "k1.web-code-create/v1");
        assert_eq!(
            value.metadata_contents,
            r#"{"status":"created","name":"alpha","version":"010101010101010101010101","language":"javascript","documentation_message_index":1,"code_message_start":2,"code_message_count":1}"#
        );
        assert!(!value.metadata_contents.contains("authority"));
        let value = overwrite(
            id(),
            Vec::new(),
            Ok("Overwritten.".into()),
            42,
            DocumentPart::Code,
            "020202020202020202020202",
        );
        assert_eq!(
            value.metadata_contents,
            r#"{"status":"overwritten","box_id":42,"part":"code","version":"020202020202020202020202"}"#
        );
    }

    #[test]
    fn check_and_publish_are_always_fresh() {
        let checked = check_failed(id(), Vec::new(), Err("bad".into()), "all diagnostics");
        assert_eq!(
            checked.metadata_contents,
            r#"{"status":"failed","fresh":true,"diagnostics":"all diagnostics"}"#
        );
        let published = publish(
            id(),
            Vec::new(),
            Ok("Published 1.2.3.".into()),
            "1.2.3",
            "object",
            "winner",
            None,
            PublishStatus::Published,
        );
        assert_eq!(
            published.metadata_contents,
            r#"{"status":"published","version":"1.2.3","source_object":"object","winning":"winner","submitted":null,"check":"fresh"}"#
        );
    }
}