kcode_k1_chat_thread_web_code_open_targets/
lib.rs1#![forbid(unsafe_code)]
2
3use kcode_k1_chat_boxes::{BoxId, ChatBox, ToolCallId};
4use kcode_k1_chat_web_code_output::{
5 DocumentPart, Language as OutputLanguage, WEB_CODE_OPEN_METADATA_TYPE, WebCodeOutput, open,
6};
7use kcode_k1_web_code_document::{CodeDocument, Language};
8use std::fmt::{Display, Formatter};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub struct OpenLayout {
12 originating_call: BoxId,
13 tool_call_id: ToolCallId,
14 code_message_count: u64,
15}
16
17impl OpenLayout {
18 pub const fn originating_call(self) -> BoxId {
19 self.originating_call
20 }
21
22 pub const fn tool_call_id(self) -> ToolCallId {
23 self.tool_call_id
24 }
25
26 pub const fn code_message_count(self) -> u64 {
27 self.code_message_count
28 }
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct OpenPresentation {
33 pub output: WebCodeOutput,
34 pub layout: OpenLayout,
35}
36
37pub fn present_open(
38 originating_call: BoxId,
39 tool_call_id: ToolCallId,
40 document: &CodeDocument,
41 authority: &str,
42 name: &str,
43 version: &str,
44) -> OpenPresentation {
45 let ranges = document.chunk_ranges();
46 let code_message_count =
47 u64::try_from(ranges.len()).expect("code chunk count must fit in a u64");
48 let documentation = std::str::from_utf8(document.documentation())
49 .expect("CodeDocument maintains UTF-8 documentation")
50 .to_owned();
51
52 let mut messages = Vec::with_capacity(ranges.len());
53 messages.push(documentation);
54 for range in &ranges[..ranges.len() - 1] {
55 messages.push(
56 std::str::from_utf8(&document.code()[range.clone()])
57 .expect("CodeDocument chunks preserve UTF-8 boundaries")
58 .to_owned(),
59 );
60 }
61
62 let final_range = ranges
63 .last()
64 .expect("CodeDocument always returns at least one code chunk");
65 let result = Ok(std::str::from_utf8(&document.code()[final_range.clone()])
66 .expect("CodeDocument chunks preserve UTF-8 boundaries")
67 .to_owned());
68 let language = match document.language() {
69 Language::JavaScript => OutputLanguage::Javascript,
70 Language::Html => OutputLanguage::Html,
71 Language::Css => OutputLanguage::Css,
72 };
73 let output = open(
74 tool_call_id,
75 messages,
76 result,
77 authority,
78 name,
79 version,
80 language,
81 code_message_count,
82 );
83
84 OpenPresentation {
85 output,
86 layout: OpenLayout {
87 originating_call,
88 tool_call_id,
89 code_message_count,
90 },
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
95pub enum TargetRejection {
96 HistoryAbsent,
97 HistoryDuplicate,
98 MalformedPayload,
99 DifferentOpen,
100 MessageOutOfLayout,
101 FailedResult,
102 NotOpenPayload,
103}
104
105impl TargetRejection {
106 pub const fn code(self) -> &'static str {
107 match self {
108 Self::HistoryAbsent => "history_absent",
109 Self::HistoryDuplicate => "history_duplicate",
110 Self::MalformedPayload => "malformed_payload",
111 Self::DifferentOpen => "different_open",
112 Self::MessageOutOfLayout => "message_out_of_layout",
113 Self::FailedResult => "failed_result",
114 Self::NotOpenPayload => "not_open_payload",
115 }
116 }
117}
118
119impl Display for TargetRejection {
120 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
121 formatter.write_str(match self {
122 Self::HistoryAbsent => "target box is absent from history",
123 Self::HistoryDuplicate => "target box ID occurs more than once in history",
124 Self::MalformedPayload => "target has malformed tool metadata",
125 Self::DifferentOpen => "target belongs to a different Open",
126 Self::MessageOutOfLayout => "target message index is outside the Open layout",
127 Self::FailedResult => "target is a failed Open result",
128 Self::NotOpenPayload => "target is not an Open payload",
129 })
130 }
131}
132
133impl std::error::Error for TargetRejection {}
134
135pub fn classify_original_target(
136 history: &[ChatBox],
137 target_id: BoxId,
138 layout: OpenLayout,
139) -> Result<DocumentPart, TargetRejection> {
140 let mut matching = history.iter().filter(|value| value.id() == target_id);
141 let target = matching.next().ok_or(TargetRejection::HistoryAbsent)?;
142 if matching.next().is_some() {
143 return Err(TargetRejection::HistoryDuplicate);
144 }
145
146 match target.tool_message_metadata() {
147 Err(_) => return Err(TargetRejection::MalformedPayload),
148 Ok(Some(message)) => {
149 if message.tool_call_id != layout.tool_call_id
150 || message.originating_call != layout.originating_call
151 {
152 return Err(TargetRejection::DifferentOpen);
153 }
154 return match message.message_index {
155 1 => Ok(DocumentPart::Documentation),
156 index if (2..=layout.code_message_count).contains(&index) => Ok(DocumentPart::Code),
157 _ => Err(TargetRejection::MessageOutOfLayout),
158 };
159 }
160 Ok(None) => {}
161 }
162
163 let result = target
164 .tool_result_v2_metadata()
165 .map_err(|_| TargetRejection::MalformedPayload)?
166 .ok_or(TargetRejection::NotOpenPayload)?;
167 if result.tool_call_id != layout.tool_call_id
168 || result.originating_call != layout.originating_call
169 {
170 return Err(TargetRejection::DifferentOpen);
171 }
172 if result.metadata_type != WEB_CODE_OPEN_METADATA_TYPE {
173 return Err(TargetRejection::NotOpenPayload);
174 }
175 if result.result.is_err() {
176 return Err(TargetRejection::FailedResult);
177 }
178
179 Ok(DocumentPart::Code)
180}