Skip to main content

hanzo_protocol/
user_input.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use serde::Serialize;
4use ts_rs::TS;
5
6/// User input
7#[non_exhaustive]
8#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS, JsonSchema)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum UserInput {
11    Text {
12        text: String,
13        /// UI-defined spans within `text` that should be treated as special elements.
14        /// These are byte ranges into the UTF-8 `text` buffer and are used to render
15        /// or persist rich input markers (e.g., image placeholders) across history
16        /// and resume without mutating the literal text.
17        #[serde(default)]
18        text_elements: Vec<TextElement>,
19    },
20    /// Pre‑encoded data: URI image.
21    Image { image_url: String },
22
23    /// Local image path provided by the user.  This will be converted to an
24    /// `Image` variant (base64 data URL) during request serialization.
25    LocalImage { path: std::path::PathBuf },
26
27    /// Skill selected by the user (name + path to SKILL.md).
28    Skill {
29        name: String,
30        path: std::path::PathBuf,
31    },
32    /// Explicit mention selected by the user (name + app://connector id).
33    Mention { name: String, path: String },
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, TS, JsonSchema)]
37pub struct TextElement {
38    /// Byte range in the parent `text` buffer that this element occupies.
39    pub byte_range: ByteRange,
40    /// Optional human-readable placeholder for the element, displayed in the UI.
41    placeholder: Option<String>,
42}
43
44impl TextElement {
45    pub fn new(byte_range: ByteRange, placeholder: Option<String>) -> Self {
46        Self {
47            byte_range,
48            placeholder,
49        }
50    }
51
52    /// Returns a copy of this element with a remapped byte range.
53    ///
54    /// The placeholder is preserved as-is; callers must ensure the new range
55    /// still refers to the same logical element (and same placeholder)
56    /// within the new text.
57    pub fn map_range<F>(&self, map: F) -> Self
58    where
59        F: FnOnce(ByteRange) -> ByteRange,
60    {
61        Self {
62            byte_range: map(self.byte_range),
63            placeholder: self.placeholder.clone(),
64        }
65    }
66
67    pub fn set_placeholder(&mut self, placeholder: Option<String>) {
68        self.placeholder = placeholder;
69    }
70
71    /// Returns the stored placeholder without falling back to the text buffer.
72    ///
73    /// This must only be used inside `From<TextElement>` implementations on equivalent
74    /// protocol types where the source text is unavailable. Prefer `placeholder(text)`
75    /// everywhere else.
76    #[doc(hidden)]
77    pub fn _placeholder_for_conversion_only(&self) -> Option<&str> {
78        self.placeholder.as_deref()
79    }
80
81    pub fn placeholder<'a>(&'a self, text: &'a str) -> Option<&'a str> {
82        self.placeholder
83            .as_deref()
84            .or_else(|| text.get(self.byte_range.start..self.byte_range.end))
85    }
86}
87
88#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, TS, JsonSchema)]
89pub struct ByteRange {
90    /// Start byte offset (inclusive) within the UTF-8 text buffer.
91    pub start: usize,
92    /// End byte offset (exclusive) within the UTF-8 text buffer.
93    pub end: usize,
94}
95
96impl From<std::ops::Range<usize>> for ByteRange {
97    fn from(range: std::ops::Range<usize>) -> Self {
98        Self {
99            start: range.start,
100            end: range.end,
101        }
102    }
103}
104