Skip to main content

kcode_k1_chat_web_code_output/
lib.rs

1#![forbid(unsafe_code)]
2
3use kcode_k1_chat_boxes::ToolCallId;
4pub use kcode_k1_web_code_ktool_protocol::{
5    Language, WEB_CODE_CHECK_METADATA_TYPE, WEB_CODE_CREATE_METADATA_TYPE,
6    WEB_CODE_DOCS_METADATA_TYPE, WEB_CODE_OPEN_METADATA_TYPE, WEB_CODE_OVERWRITE_METADATA_TYPE,
7    WEB_CODE_PUBLISH_METADATA_TYPE,
8};
9use serde::Serialize;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct WebCodeOutput {
13    pub tool_call_id: ToolCallId,
14    pub messages: Vec<String>,
15    pub result: Result<String, String>,
16    pub metadata_type: String,
17    pub metadata_contents: String,
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum PublishStatus {
22    Published,
23    Idempotent,
24    Conflict,
25}
26impl PublishStatus {
27    pub const fn as_str(self) -> &'static str {
28        match self {
29            Self::Published => "published",
30            Self::Idempotent => "idempotent",
31            Self::Conflict => "conflict",
32        }
33    }
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum DocumentPart {
38    Documentation,
39    Code,
40}
41impl DocumentPart {
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Self::Documentation => "documentation",
45            Self::Code => "code",
46        }
47    }
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum Operation {
52    Create,
53    Docs,
54    Open,
55    Overwrite,
56    Check,
57    Publish,
58}
59impl Operation {
60    pub const fn metadata_type(self) -> &'static str {
61        match self {
62            Self::Create => WEB_CODE_CREATE_METADATA_TYPE,
63            Self::Docs => WEB_CODE_DOCS_METADATA_TYPE,
64            Self::Open => WEB_CODE_OPEN_METADATA_TYPE,
65            Self::Overwrite => WEB_CODE_OVERWRITE_METADATA_TYPE,
66            Self::Check => WEB_CODE_CHECK_METADATA_TYPE,
67            Self::Publish => WEB_CODE_PUBLISH_METADATA_TYPE,
68        }
69    }
70}
71
72#[derive(Serialize)]
73struct DocsMetadata<'a> {
74    status: &'static str,
75    name: &'a str,
76    version: &'a str,
77}
78#[derive(Serialize)]
79struct SourceMetadata<'a> {
80    status: &'static str,
81    name: &'a str,
82    version: &'a str,
83    language: &'static str,
84    documentation_message_index: u64,
85    code_message_start: u64,
86    code_message_count: u64,
87}
88#[derive(Serialize)]
89struct OverwriteMetadata<'a> {
90    status: &'static str,
91    box_id: u64,
92    part: &'static str,
93    version: &'a str,
94}
95#[derive(Serialize)]
96struct CheckMetadata<'a> {
97    status: &'static str,
98    fresh: bool,
99    diagnostics: Option<&'a str>,
100}
101#[derive(Serialize)]
102struct PublishMetadata<'a> {
103    status: &'static str,
104    version: &'a str,
105    source_object: &'a str,
106    winning: &'a str,
107    submitted: Option<&'a str>,
108    check: &'static str,
109}
110#[derive(Serialize)]
111struct ErrorMetadata<'a> {
112    status: &'static str,
113    code: &'a str,
114    source_object: Option<&'a str>,
115}
116
117pub fn docs(
118    tool_call_id: ToolCallId,
119    messages: Vec<String>,
120    result: Result<String, String>,
121    name: &str,
122    version: &str,
123) -> WebCodeOutput {
124    build(
125        tool_call_id,
126        messages,
127        result,
128        WEB_CODE_DOCS_METADATA_TYPE,
129        &DocsMetadata {
130            status: "found",
131            name,
132            version,
133        },
134    )
135}
136
137#[allow(clippy::too_many_arguments)]
138pub fn create(
139    tool_call_id: ToolCallId,
140    messages: Vec<String>,
141    result: Result<String, String>,
142    name: &str,
143    version: &str,
144    language: Language,
145    code_message_count: u64,
146) -> WebCodeOutput {
147    source(
148        tool_call_id,
149        messages,
150        result,
151        WEB_CODE_CREATE_METADATA_TYPE,
152        "created",
153        name,
154        version,
155        language,
156        code_message_count,
157    )
158}
159
160#[allow(clippy::too_many_arguments)]
161pub fn open(
162    tool_call_id: ToolCallId,
163    messages: Vec<String>,
164    result: Result<String, String>,
165    name: &str,
166    version: &str,
167    language: Language,
168    code_message_count: u64,
169) -> WebCodeOutput {
170    source(
171        tool_call_id,
172        messages,
173        result,
174        WEB_CODE_OPEN_METADATA_TYPE,
175        "opened",
176        name,
177        version,
178        language,
179        code_message_count,
180    )
181}
182
183#[allow(clippy::too_many_arguments)]
184fn source(
185    tool_call_id: ToolCallId,
186    messages: Vec<String>,
187    result: Result<String, String>,
188    metadata_type: &str,
189    status: &'static str,
190    name: &str,
191    version: &str,
192    language: Language,
193    code_message_count: u64,
194) -> WebCodeOutput {
195    build(
196        tool_call_id,
197        messages,
198        result,
199        metadata_type,
200        &SourceMetadata {
201            status,
202            name,
203            version,
204            language: language.as_str(),
205            documentation_message_index: 1,
206            code_message_start: 2,
207            code_message_count,
208        },
209    )
210}
211
212pub fn overwrite(
213    tool_call_id: ToolCallId,
214    messages: Vec<String>,
215    result: Result<String, String>,
216    box_id: u64,
217    part: DocumentPart,
218    version: &str,
219) -> WebCodeOutput {
220    build(
221        tool_call_id,
222        messages,
223        result,
224        WEB_CODE_OVERWRITE_METADATA_TYPE,
225        &OverwriteMetadata {
226            status: "overwritten",
227            box_id,
228            part: part.as_str(),
229            version,
230        },
231    )
232}
233
234pub fn check_success(
235    tool_call_id: ToolCallId,
236    messages: Vec<String>,
237    result: Result<String, String>,
238) -> WebCodeOutput {
239    build(
240        tool_call_id,
241        messages,
242        result,
243        WEB_CODE_CHECK_METADATA_TYPE,
244        &CheckMetadata {
245            status: "success",
246            fresh: true,
247            diagnostics: None,
248        },
249    )
250}
251
252pub fn check_failed(
253    tool_call_id: ToolCallId,
254    messages: Vec<String>,
255    result: Result<String, String>,
256    diagnostics: &str,
257) -> WebCodeOutput {
258    build(
259        tool_call_id,
260        messages,
261        result,
262        WEB_CODE_CHECK_METADATA_TYPE,
263        &CheckMetadata {
264            status: "failed",
265            fresh: true,
266            diagnostics: Some(diagnostics),
267        },
268    )
269}
270
271#[allow(clippy::too_many_arguments)]
272pub fn publish(
273    tool_call_id: ToolCallId,
274    messages: Vec<String>,
275    result: Result<String, String>,
276    version: &str,
277    source_object: &str,
278    winning: &str,
279    submitted: Option<&str>,
280    status: PublishStatus,
281) -> WebCodeOutput {
282    build(
283        tool_call_id,
284        messages,
285        result,
286        WEB_CODE_PUBLISH_METADATA_TYPE,
287        &PublishMetadata {
288            status: status.as_str(),
289            version,
290            source_object,
291            winning,
292            submitted,
293            check: "fresh",
294        },
295    )
296}
297
298pub fn error(
299    tool_call_id: ToolCallId,
300    messages: Vec<String>,
301    result: Result<String, String>,
302    operation: Operation,
303    code: &str,
304    source_object: Option<&str>,
305) -> WebCodeOutput {
306    build(
307        tool_call_id,
308        messages,
309        result,
310        operation.metadata_type(),
311        &ErrorMetadata {
312            status: "error",
313            code,
314            source_object,
315        },
316    )
317}
318
319fn build(
320    tool_call_id: ToolCallId,
321    messages: Vec<String>,
322    result: Result<String, String>,
323    metadata_type: &str,
324    metadata: &impl Serialize,
325) -> WebCodeOutput {
326    WebCodeOutput {
327        tool_call_id,
328        messages,
329        result,
330        metadata_type: metadata_type.to_owned(),
331        metadata_contents: serde_json::to_string(metadata)
332            .expect("fixed WebCode metadata serializes"),
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    fn id() -> ToolCallId {
340        ToolCallId::new([1; 12], 7)
341    }
342    #[test]
343    fn metadata_is_exact_and_authority_free() {
344        let value = create(
345            id(),
346            vec!["docs".into(), "code".into()],
347            Ok("Created 010101010101010101010101.".into()),
348            "alpha",
349            "010101010101010101010101",
350            Language::JavaScript,
351            1,
352        );
353        assert_eq!(value.metadata_type, "k1.web-code-create/v1");
354        assert_eq!(
355            value.metadata_contents,
356            r#"{"status":"created","name":"alpha","version":"010101010101010101010101","language":"javascript","documentation_message_index":1,"code_message_start":2,"code_message_count":1}"#
357        );
358        assert!(!value.metadata_contents.contains("authority"));
359        let value = overwrite(
360            id(),
361            Vec::new(),
362            Ok("Overwritten.".into()),
363            42,
364            DocumentPart::Code,
365            "020202020202020202020202",
366        );
367        assert_eq!(
368            value.metadata_contents,
369            r#"{"status":"overwritten","box_id":42,"part":"code","version":"020202020202020202020202"}"#
370        );
371    }
372
373    #[test]
374    fn check_and_publish_are_always_fresh() {
375        let checked = check_failed(id(), Vec::new(), Err("bad".into()), "all diagnostics");
376        assert_eq!(
377            checked.metadata_contents,
378            r#"{"status":"failed","fresh":true,"diagnostics":"all diagnostics"}"#
379        );
380        let published = publish(
381            id(),
382            Vec::new(),
383            Ok("Published 1.2.3.".into()),
384            "1.2.3",
385            "object",
386            "winner",
387            None,
388            PublishStatus::Published,
389        );
390        assert_eq!(
391            published.metadata_contents,
392            r#"{"status":"published","version":"1.2.3","source_object":"object","winning":"winner","submitted":null,"check":"fresh"}"#
393        );
394    }
395}