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