1#![allow(clippy::enum_variant_names)]
28
29use serde::{Deserialize, Serialize};
30
31use crate::{File, FileHandle, FilesError};
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35#[serde(rename_all = "lowercase")]
36pub enum Role {
37 User,
39 Model,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45#[serde(untagged)]
46pub enum Part {
47 Text {
49 text: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 thought: Option<bool>,
54 #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
56 thought_signature: Option<String>,
57 },
58 InlineData {
59 #[serde(rename = "inlineData")]
61 inline_data: Blob,
62 #[serde(skip_serializing_if = "Option::is_none")]
65 media_resolution: Option<super::generation::model::MediaResolution>,
66 },
67 FunctionCall {
69 #[serde(rename = "functionCall")]
71 function_call: super::tools::FunctionCall,
72 #[serde(rename = "thoughtSignature", skip_serializing_if = "Option::is_none")]
74 thought_signature: Option<String>,
75 },
76 FunctionResponse {
78 #[serde(rename = "functionResponse")]
80 function_response: super::tools::FunctionResponse,
81 },
82 FileData {
84 #[serde(rename = "fileData")]
85 file_data: FileData,
86 },
87 ExecutableCode {
89 #[serde(rename = "executableCode")]
91 executable_code: super::tools::ExecutableCode,
92 },
93 CodeExecutionResult {
95 #[serde(rename = "codeExecutionResult")]
97 code_execution_result: super::tools::CodeExecutionResult,
98 },
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111#[serde(rename_all = "camelCase")]
112pub struct FileData {
113 pub mime_type: String,
115 pub file_uri: String,
117}
118
119impl TryFrom<&FileHandle> for FileData {
120 type Error = FilesError;
121
122 fn try_from(file_handle: &FileHandle) -> Result<Self, Self::Error> {
123 let File { mime_type, uri, .. } = file_handle.get_file_meta();
124
125 let none_fields: Vec<_> = [
126 mime_type.is_none().then_some("mime_type"),
127 uri.is_none().then_some("uri"),
128 ]
129 .into_iter()
130 .flatten()
131 .map(String::from)
132 .collect();
133
134 if !none_fields.is_empty() {
135 return Err(FilesError::Incomplete {
136 fields: none_fields,
137 });
138 }
139
140 Ok(Self {
141 mime_type: mime_type
142 .as_ref()
143 .expect("Some-ness checked above")
144 .to_string(),
145 file_uri: uri.as_ref().expect("Some-ness checked above").to_string(),
146 })
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153pub struct Blob {
154 pub mime_type: String,
156 pub data: String,
158}
159
160impl Blob {
161 pub fn new(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
163 Self {
164 mime_type: mime_type.into(),
165 data: data.into(),
166 }
167 }
168}
169
170#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
172#[serde(rename_all = "camelCase")]
173pub struct Content {
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub parts: Option<Vec<Part>>,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub role: Option<Role>,
180}
181
182impl Content {
183 pub fn text(text: impl Into<String>) -> Self {
185 Self {
186 parts: Some(vec![Part::Text {
187 text: text.into(),
188 thought: None,
189 thought_signature: None,
190 }]),
191 role: None,
192 }
193 }
194
195 pub fn function_call(function_call: super::tools::FunctionCall) -> Self {
197 Self {
198 parts: Some(vec![Part::FunctionCall {
199 function_call,
200 thought_signature: None,
201 }]),
202 role: None,
203 }
204 }
205
206 pub fn function_call_with_thought(
208 function_call: super::tools::FunctionCall,
209 thought_signature: impl Into<String>,
210 ) -> Self {
211 Self {
212 parts: Some(vec![Part::FunctionCall {
213 function_call,
214 thought_signature: Some(thought_signature.into()),
215 }]),
216 role: None,
217 }
218 }
219
220 pub fn text_with_thought_signature(
222 text: impl Into<String>,
223 thought_signature: impl Into<String>,
224 ) -> Self {
225 Self {
226 parts: Some(vec![Part::Text {
227 text: text.into(),
228 thought: None,
229 thought_signature: Some(thought_signature.into()),
230 }]),
231 role: None,
232 }
233 }
234
235 pub fn thought_with_signature(
237 text: impl Into<String>,
238 thought_signature: impl Into<String>,
239 ) -> Self {
240 Self {
241 parts: Some(vec![Part::Text {
242 text: text.into(),
243 thought: Some(true),
244 thought_signature: Some(thought_signature.into()),
245 }]),
246 role: None,
247 }
248 }
249
250 pub fn function_response(function_response: super::tools::FunctionResponse) -> Self {
252 Self {
253 parts: Some(vec![Part::FunctionResponse { function_response }]),
254 role: None,
255 }
256 }
257
258 pub fn function_response_json(name: impl Into<String>, response: serde_json::Value) -> Self {
260 Self {
261 parts: Some(vec![Part::FunctionResponse {
262 function_response: super::tools::FunctionResponse::new(name, response),
263 }]),
264 role: None,
265 }
266 }
267
268 pub fn inline_data(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
270 Self {
271 parts: Some(vec![Part::InlineData {
272 inline_data: Blob::new(mime_type, data),
273 media_resolution: None,
274 }]),
275 role: None,
276 }
277 }
278
279 pub fn inline_data_with_resolution(
281 mime_type: impl Into<String>,
282 data: impl Into<String>,
283 resolution: super::generation::model::MediaResolutionLevel,
284 ) -> Self {
285 Self {
286 parts: Some(vec![Part::InlineData {
287 inline_data: Blob::new(mime_type, data),
288 media_resolution: Some(super::generation::model::MediaResolution {
289 level: resolution,
290 }),
291 }]),
292 role: None,
293 }
294 }
295
296 pub fn text_with_file(
298 text: impl Into<String>,
299 file_handle: &FileHandle,
300 ) -> Result<Self, FilesError> {
301 Ok(Self {
302 parts: Some(vec![
303 Part::Text {
304 text: text.into(),
305 thought: None,
306 thought_signature: None,
307 },
308 Part::FileData {
309 file_data: FileData::try_from(file_handle)?,
310 },
311 ]),
312 role: None,
313 })
314 }
315
316 pub fn with_role(mut self, role: Role) -> Self {
318 self.role = Some(role);
319 self
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct Message {
326 pub content: Content,
328 pub role: Role,
330}
331
332impl Message {
333 pub fn user(text: impl Into<String>) -> Self {
335 Self {
336 content: Content::text(text).with_role(Role::User),
337 role: Role::User,
338 }
339 }
340
341 pub fn model(text: impl Into<String>) -> Self {
343 Self {
344 content: Content::text(text).with_role(Role::Model),
345 role: Role::Model,
346 }
347 }
348
349 pub fn embed(text: impl Into<String>) -> Self {
351 Self {
352 content: Content::text(text),
353 role: Role::Model,
354 }
355 }
356
357 pub fn function(name: impl Into<String>, response: serde_json::Value) -> Self {
359 Self {
360 content: Content::function_response_json(name, response).with_role(Role::Model),
361 role: Role::Model,
362 }
363 }
364
365 pub fn function_str(
367 name: impl Into<String>,
368 response: impl Into<String>,
369 ) -> Result<Self, serde_json::Error> {
370 let response_str = response.into();
371 let json = serde_json::from_str(&response_str)?;
372 Ok(Self {
373 content: Content::function_response_json(name, json).with_role(Role::Model),
374 role: Role::Model,
375 })
376 }
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
381#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
382pub enum Modality {
383 ModalityUnspecified,
385 Document,
387 Text,
389 Image,
391 Audio,
393 Video,
395 #[serde(untagged)]
396 Other(String),
397}