Skip to main content

kcode_k1_chat_thread_web_code_open_targets/
lib.rs

1#![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}