#![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_FILE_METADATA_TYPE,
WEB_CODE_CREATE_METADATA_TYPE, WEB_CODE_DELETE_FILE_METADATA_TYPE, WEB_CODE_DOCS_METADATA_TYPE,
WEB_CODE_OPEN_METADATA_TYPE, WEB_CODE_OVERWRITE_METADATA_TYPE, WEB_CODE_PUBLISH_METADATA_TYPE,
WEB_CODE_RENAME_FILE_METADATA_TYPE,
};
use serde::Serialize;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WebCodeOutput {
pub tool_call_id: ToolCallId,
pub messages: Vec<String>,
pub message_titles: Vec<Option<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 Operation {
Create,
Docs,
Open,
Overwrite,
CreateFile,
RenameFile,
DeleteFile,
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::CreateFile => WEB_CODE_CREATE_FILE_METADATA_TYPE,
Self::RenameFile => WEB_CODE_RENAME_FILE_METADATA_TYPE,
Self::DeleteFile => WEB_CODE_DELETE_FILE_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,
file_message_start: u64,
file_message_count: u64,
paths: &'a [String],
}
#[derive(Serialize)]
struct OverwriteMetadata<'a> {
status: &'static str,
box_id: u64,
path: &'a str,
version: &'a str,
}
#[derive(Serialize)]
struct CreateFileMetadata<'a> {
status: &'static str,
box_id: u64,
path: &'a str,
version: &'a str,
}
#[derive(Serialize)]
struct RenameFileMetadata<'a> {
status: &'static str,
box_id: u64,
path: &'a str,
destination: &'a str,
version: &'a str,
}
#[derive(Serialize)]
struct DeleteFileMetadata<'a> {
status: &'static str,
box_id: u64,
path: &'a 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,
Vec::new(),
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,
paths: &[String],
) -> WebCodeOutput {
source(
tool_call_id,
messages,
result,
WEB_CODE_CREATE_METADATA_TYPE,
"created",
name,
version,
language,
paths,
)
}
#[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,
paths: &[String],
) -> WebCodeOutput {
source(
tool_call_id,
messages,
result,
WEB_CODE_OPEN_METADATA_TYPE,
"opened",
name,
version,
language,
paths,
)
}
#[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,
paths: &[String],
) -> WebCodeOutput {
assert_eq!(
messages.len(),
paths.len(),
"every source message must have one path"
);
let count = u64::try_from(paths.len()).expect("file count fits u64");
let titles = paths
.iter()
.map(|path| Some(format!("File: {path}")))
.collect();
build(
tool_call_id,
messages,
titles,
result,
metadata_type,
&SourceMetadata {
status,
name,
version,
language: language.as_str(),
file_message_start: 1,
file_message_count: count,
paths,
},
)
}
pub fn overwrite(
tool_call_id: ToolCallId,
result: Result<String, String>,
box_id: u64,
path: &str,
version: &str,
) -> WebCodeOutput {
build(
tool_call_id,
Vec::new(),
Vec::new(),
result,
WEB_CODE_OVERWRITE_METADATA_TYPE,
&OverwriteMetadata {
status: "overwritten",
box_id,
path,
version,
},
)
}
pub fn create_file(
tool_call_id: ToolCallId,
contents: String,
result: Result<String, String>,
box_id: u64,
path: &str,
version: &str,
) -> WebCodeOutput {
build(
tool_call_id,
vec![contents],
vec![Some(format!("File: {path}"))],
result,
WEB_CODE_CREATE_FILE_METADATA_TYPE,
&CreateFileMetadata {
status: "created",
box_id,
path,
version,
},
)
}
#[allow(clippy::too_many_arguments)]
pub fn rename_file(
tool_call_id: ToolCallId,
contents: String,
result: Result<String, String>,
box_id: u64,
path: &str,
destination: &str,
version: &str,
) -> WebCodeOutput {
build(
tool_call_id,
vec![contents],
vec![Some(format!("File: {destination}"))],
result,
WEB_CODE_RENAME_FILE_METADATA_TYPE,
&RenameFileMetadata {
status: "renamed",
box_id,
path,
destination,
version,
},
)
}
pub fn delete_file(
tool_call_id: ToolCallId,
result: Result<String, String>,
box_id: u64,
path: &str,
version: &str,
) -> WebCodeOutput {
build(
tool_call_id,
Vec::new(),
Vec::new(),
result,
WEB_CODE_DELETE_FILE_METADATA_TYPE,
&DeleteFileMetadata {
status: "deleted",
box_id,
path,
version,
},
)
}
pub fn check_success(
tool_call_id: ToolCallId,
messages: Vec<String>,
result: Result<String, String>,
) -> WebCodeOutput {
build(
tool_call_id,
messages,
Vec::new(),
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,
Vec::new(),
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,
Vec::new(),
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,
Vec::new(),
result,
operation.metadata_type(),
&ErrorMetadata {
status: "error",
code,
source_object,
},
)
}
fn build(
tool_call_id: ToolCallId,
messages: Vec<String>,
mut message_titles: Vec<Option<String>>,
result: Result<String, String>,
metadata_type: &str,
metadata: &impl Serialize,
) -> WebCodeOutput {
if message_titles.is_empty() {
message_titles.resize(messages.len(), None);
}
assert_eq!(
messages.len(),
message_titles.len(),
"every WebCode message must have one title slot"
);
WebCodeOutput {
tool_call_id,
messages,
message_titles,
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 full_source_has_exact_parallel_file_titles() {
let paths = vec!["Documentation.md".to_owned(), "src/a.odd".to_owned()];
let value = create(
id(),
vec!["docs".into(), "code".into()],
Ok("created".into()),
"alpha",
"010101010101010101010101",
Language::JavaScript,
&paths,
);
assert_eq!(
value.message_titles,
[
Some("File: Documentation.md".to_owned()),
Some("File: src/a.odd".to_owned())
]
);
assert!(value.metadata_contents.contains("\"file_message_count\":2"));
assert!(!value.metadata_contents.contains("authority"));
}
#[test]
fn every_operation_has_its_exact_metadata_type() {
assert_eq!(
Operation::CreateFile.metadata_type(),
"k1.web-code-create-file/v2"
);
assert_eq!(
Operation::RenameFile.metadata_type(),
"k1.web-code-rename-file/v2"
);
assert_eq!(
Operation::DeleteFile.metadata_type(),
"k1.web-code-delete-file/v2"
);
let value = overwrite(
id(),
Ok("Overwritten.".into()),
42,
"nested/file.js",
"020202020202020202020202",
);
assert_eq!(
value.metadata_contents,
r#"{"status":"overwritten","box_id":42,"path":"nested/file.js","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"}"#
);
assert!(checked.message_titles.is_empty());
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"}"#
);
}
}