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}