Skip to main content

codex_protocol/
user_input.rs

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