Skip to main content

kimi_wire/protocol/
content.rs

1use serde::{Deserialize, Serialize};
2
3// ============================================================================
4// UserInput
5// ============================================================================
6
7/// User input can be plain text or an array of content parts.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9#[serde(untagged)]
10pub enum UserInput {
11    /// Plain text input.
12    Text(String),
13    /// Structured content parts (text, images, audio, video).
14    Parts(Vec<ContentPart>),
15}
16
17impl From<String> for UserInput {
18    fn from(value: String) -> Self {
19        UserInput::Text(value)
20    }
21}
22
23impl From<&str> for UserInput {
24    fn from(value: &str) -> Self {
25        UserInput::Text(value.to_string())
26    }
27}
28
29impl From<Vec<ContentPart>> for UserInput {
30    fn from(value: Vec<ContentPart>) -> Self {
31        UserInput::Parts(value)
32    }
33}
34
35// ============================================================================
36// ContentPart
37// ============================================================================
38
39/// A content part in a message.
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41#[serde(tag = "type", rename_all = "snake_case")]
42pub enum ContentPart {
43    /// Plain text content.
44    Text(TextPart),
45    /// Thinking / reasoning content (may be encrypted).
46    Think(ThinkPart),
47    /// Image referenced by URL or data URI.
48    #[serde(rename = "image_url")]
49    ImageUrl(ImageUrlPart),
50    /// Audio referenced by URL or data URI.
51    #[serde(rename = "audio_url")]
52    AudioUrl(AudioUrlPart),
53    /// Video referenced by URL or data URI.
54    #[serde(rename = "video_url")]
55    VideoUrl(VideoUrlPart),
56}
57
58/// Text content part.
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
60pub struct TextPart {
61    /// The text content.
62    pub text: String,
63}
64
65/// Thinking content part.
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub struct ThinkPart {
68    /// The thinking / reasoning text.
69    pub think: String,
70    /// Encrypted thinking content or signature.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub encrypted: Option<String>,
73}
74
75/// Image URL content part wrapper.
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
77pub struct ImageUrlPart {
78    /// Image media URL.
79    pub image_url: MediaUrl,
80}
81
82/// Audio URL content part wrapper.
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84pub struct AudioUrlPart {
85    /// Audio media URL.
86    pub audio_url: MediaUrl,
87}
88
89/// Video URL content part wrapper.
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct VideoUrlPart {
92    /// Video media URL.
93    pub video_url: MediaUrl,
94}
95
96/// A media URL with an optional ID.
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
98pub struct MediaUrl {
99    /// URL or data URI (e.g. `data:image/png;base64,...`).
100    pub url: String,
101    /// Optional ID for distinguishing different media items.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub id: Option<String>,
104}
105
106// ============================================================================
107// DisplayBlock
108// ============================================================================
109
110/// A display block shown to the user in tool results or approval requests.
111///
112/// This struct-based design matches the official Go SDK and avoids tag
113/// conflicts when handling unknown block types.
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115pub struct DisplayBlock {
116    /// Block type discriminator.
117    #[serde(rename = "type")]
118    pub block_type: DisplayBlockType,
119    /// Text content (used by `Brief` and `Shell`).
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub text: Option<String>,
122    /// File path (used by `Diff`).
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub path: Option<String>,
125    /// Old text for a diff.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub old_text: Option<String>,
128    /// New text for a diff.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub new_text: Option<String>,
131    /// Todo list items.
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub items: Option<Vec<TodoDisplayItem>>,
134    /// Language identifier for syntax highlighting (e.g. "sh", "powershell").
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub language: Option<String>,
137    /// Shell command string.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub command: Option<String>,
140    /// Raw data for unrecognized block types.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub data: Option<serde_json::Value>,
143}
144
145/// Discriminator for [`DisplayBlock`] types.
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
147#[serde(rename_all = "snake_case")]
148pub enum DisplayBlockType {
149    /// Brief textual summary.
150    Brief,
151    /// Diff between old and new text.
152    Diff,
153    /// Todo list.
154    Todo,
155    /// Shell command or output.
156    Shell,
157    /// Unknown block type.
158    #[serde(rename = "unknown")]
159    Unknown,
160}
161
162/// A single item in a todo display block.
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164pub struct TodoDisplayItem {
165    /// Item title.
166    pub title: String,
167    /// Completion status.
168    pub status: TodoStatus,
169}
170
171/// Status of a todo display item.
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
173#[serde(rename_all = "snake_case")]
174pub enum TodoStatus {
175    /// Not started.
176    Pending,
177    /// In progress.
178    InProgress,
179    /// Completed.
180    Done,
181}
182
183// ============================================================================
184// ToolReturnValue
185// ============================================================================
186
187/// The result of a tool execution.
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub struct ToolReturnValue {
190    /// Whether the tool execution failed.
191    pub is_error: bool,
192    /// Output returned to the model. Can be plain text or content parts.
193    pub output: ToolOutput,
194    /// Explanatory message for the model.
195    pub message: String,
196    /// Display blocks shown to the user.
197    pub display: Vec<DisplayBlock>,
198    /// Extra debug info.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub extras: Option<serde_json::Value>,
201}
202
203/// Tool output can be plain text or an array of content parts.
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
205#[serde(untagged)]
206pub enum ToolOutput {
207    /// Plain text output.
208    Text(String),
209    /// Structured content parts.
210    Parts(Vec<ContentPart>),
211}
212
213impl From<String> for ToolOutput {
214    fn from(value: String) -> Self {
215        ToolOutput::Text(value)
216    }
217}
218
219impl From<&str> for ToolOutput {
220    fn from(value: &str) -> Self {
221        ToolOutput::Text(value.to_string())
222    }
223}
224
225impl From<Vec<ContentPart>> for ToolOutput {
226    fn from(value: Vec<ContentPart>) -> Self {
227        ToolOutput::Parts(value)
228    }
229}