1use crate::OneOrMany;
5use crate::completion::{self, CompletionError, CompletionRequest, GetTokenUsage};
6use crate::http_client::HttpClientExt;
7use crate::message::{self, MimeType, Reasoning};
8use crate::telemetry::SpanCombinator;
9use serde_json::{Map, Value};
10use tracing::{Level, enabled, info_span};
11use tracing_futures::Instrument;
12use url::form_urlencoded;
13
14use super::client::InteractionsClient;
15
16pub mod streaming;
18pub use interactions_api_types::*;
19
20#[derive(Clone, Debug)]
26pub struct InteractionsCompletionModel<T = reqwest::Client> {
27 pub(crate) client: InteractionsClient<T>,
28 pub model: String,
29}
30
31impl<T> InteractionsCompletionModel<T> {
32 pub fn new(client: InteractionsClient<T>, model: impl Into<String>) -> Self {
34 Self {
35 client,
36 model: model.into(),
37 }
38 }
39
40 pub fn with_model(client: InteractionsClient<T>, model: &str) -> Self {
42 Self {
43 client,
44 model: model.to_string(),
45 }
46 }
47
48 pub fn generate_content_api(self) -> super::completion::CompletionModel<T> {
50 super::completion::CompletionModel::with_model(
51 self.client.generate_content_api(),
52 &self.model,
53 )
54 }
55
56 pub(crate) fn create_completion_request(
57 &self,
58 completion_request: CompletionRequest,
59 stream_override: Option<bool>,
60 ) -> Result<CreateInteractionRequest, CompletionError> {
61 create_request_body(self.model.clone(), completion_request, stream_override)
62 }
63}
64
65impl<T> InteractionsCompletionModel<T>
66where
67 T: HttpClientExt + Clone + std::fmt::Debug + Default + 'static,
68{
69 pub async fn create_interaction(
71 &self,
72 completion_request: CompletionRequest,
73 ) -> Result<Interaction, CompletionError> {
74 let request = self.create_completion_request(completion_request, Some(false))?;
75 self.client.create_interaction(request).await
76 }
77
78 pub async fn get_interaction(
80 &self,
81 interaction_id: impl AsRef<str>,
82 ) -> Result<Interaction, CompletionError> {
83 self.client.get_interaction(interaction_id).await
84 }
85
86 pub async fn stream_interaction_events(
88 &self,
89 completion_request: CompletionRequest,
90 ) -> Result<streaming::InteractionEventStream, CompletionError> {
91 let request = self.create_completion_request(completion_request, Some(true))?;
92 self.client.stream_interaction_events(request).await
93 }
94
95 pub async fn stream_interaction_events_by_id(
97 &self,
98 interaction_id: impl AsRef<str>,
99 last_event_id: Option<&str>,
100 ) -> Result<streaming::InteractionEventStream, CompletionError> {
101 self.client
102 .stream_interaction_events_by_id(interaction_id, last_event_id)
103 .await
104 }
105}
106
107impl<T> completion::CompletionModel for InteractionsCompletionModel<T>
108where
109 T: HttpClientExt + Clone + std::fmt::Debug + Default + 'static,
110{
111 type Response = Interaction;
112 type StreamingResponse = streaming::StreamingCompletionResponse;
113 type Client = InteractionsClient<T>;
114
115 fn make(client: &Self::Client, model: impl Into<String>) -> Self {
116 Self::new(client.clone(), model)
117 }
118
119 async fn completion(
120 &self,
121 completion_request: CompletionRequest,
122 ) -> Result<completion::CompletionResponse<Interaction>, CompletionError> {
123 let span = if tracing::Span::current().is_disabled() {
124 info_span!(
125 target: "rig::completions",
126 "interactions",
127 gen_ai.operation.name = "interactions",
128 gen_ai.provider.name = "gcp.gemini",
129 gen_ai.request.model = self.model,
130 gen_ai.system_instructions = &completion_request.preamble,
131 gen_ai.response.id = tracing::field::Empty,
132 gen_ai.response.model = tracing::field::Empty,
133 gen_ai.usage.output_tokens = tracing::field::Empty,
134 gen_ai.usage.input_tokens = tracing::field::Empty,
135 )
136 } else {
137 tracing::Span::current()
138 };
139
140 let request = self.create_completion_request(completion_request, Some(false))?;
141
142 if enabled!(Level::TRACE) {
143 tracing::trace!(
144 target: "rig::completions",
145 "Gemini interactions completion request: {}",
146 serde_json::to_string_pretty(&request)?
147 );
148 }
149
150 let body = serde_json::to_vec(&request)?;
151 let request = self
152 .client
153 .post("/v1beta/interactions")?
154 .body(body)
155 .map_err(|e| CompletionError::HttpError(e.into()))?;
156
157 async move {
158 let response = self.client.send::<_, Vec<u8>>(request).await?;
159
160 if response.status().is_success() {
161 let response_body = response
162 .into_body()
163 .await
164 .map_err(CompletionError::HttpError)?;
165
166 let response_text = String::from_utf8_lossy(&response_body).to_string();
167
168 let response: Interaction =
169 serde_json::from_slice(&response_body).map_err(|err| {
170 tracing::error!(
171 error = %err,
172 body = %response_text,
173 "Failed to deserialize Gemini interactions response"
174 );
175 CompletionError::JsonError(err)
176 })?;
177
178 let span = tracing::Span::current();
179 span.record_response_metadata(&response);
180 span.record_token_usage(&response);
181
182 if enabled!(Level::TRACE) {
183 tracing::trace!(
184 target: "rig::completions",
185 "Gemini interactions completion response: {}",
186 serde_json::to_string_pretty(&response)?
187 );
188 }
189
190 response.try_into()
191 } else {
192 let text = String::from_utf8_lossy(
193 &response
194 .into_body()
195 .await
196 .map_err(CompletionError::HttpError)?,
197 )
198 .into();
199
200 Err(CompletionError::ProviderError(text))
201 }
202 }
203 .instrument(span)
204 .await
205 }
206
207 async fn stream(
208 &self,
209 request: CompletionRequest,
210 ) -> Result<
211 crate::streaming::StreamingCompletionResponse<Self::StreamingResponse>,
212 CompletionError,
213 > {
214 InteractionsCompletionModel::stream(self, request).await
215 }
216}
217
218impl<T> InteractionsClient<T>
219where
220 T: HttpClientExt + Clone + std::fmt::Debug + Default + 'static,
221{
222 pub async fn create_interaction(
224 &self,
225 request: CreateInteractionRequest,
226 ) -> Result<Interaction, CompletionError> {
227 if request.stream == Some(true) {
228 return Err(CompletionError::RequestError(Box::new(
229 std::io::Error::new(
230 std::io::ErrorKind::InvalidInput,
231 "stream=true requires stream_interaction_events",
232 ),
233 )));
234 }
235
236 let body = serde_json::to_vec(&request)?;
237 let request = self
238 .post("/v1beta/interactions")?
239 .body(body)
240 .map_err(|e| CompletionError::HttpError(e.into()))?;
241
242 send_interaction_request(self, request).await
243 }
244
245 pub async fn get_interaction(
247 &self,
248 interaction_id: impl AsRef<str>,
249 ) -> Result<Interaction, CompletionError> {
250 let path = format!("/v1beta/interactions/{}", interaction_id.as_ref());
251 let request = self
252 .get(path)?
253 .body(Vec::new())
254 .map_err(|e| CompletionError::HttpError(e.into()))?;
255
256 send_interaction_request(self, request).await
257 }
258
259 pub async fn stream_interaction_events(
261 &self,
262 mut request: CreateInteractionRequest,
263 ) -> Result<streaming::InteractionEventStream, CompletionError> {
264 request.stream = Some(true);
265 let body = serde_json::to_vec(&request)?;
266 let request = self
267 .post_sse("/v1beta/interactions")?
268 .header("Content-Type", "application/json")
269 .body(body)
270 .map_err(|e| CompletionError::HttpError(e.into()))?;
271
272 Ok(streaming::stream_interaction_events(self.clone(), request))
273 }
274
275 pub async fn stream_interaction_events_by_id(
277 &self,
278 interaction_id: impl AsRef<str>,
279 last_event_id: Option<&str>,
280 ) -> Result<streaming::InteractionEventStream, CompletionError> {
281 let path = build_interaction_stream_path(interaction_id.as_ref(), last_event_id);
282 let request = self
283 .get_sse(path)?
284 .body(Vec::new())
285 .map_err(|e| CompletionError::HttpError(e.into()))?;
286
287 Ok(streaming::stream_interaction_events(self.clone(), request))
288 }
289}
290
291pub(crate) fn create_request_body(
292 model: String,
293 completion_request: CompletionRequest,
294 stream_override: Option<bool>,
295) -> Result<CreateInteractionRequest, CompletionError> {
296 let mut history = Vec::new();
297 if let Some(docs) = completion_request.normalized_documents() {
298 history.push(docs);
299 }
300 history.extend(completion_request.chat_history);
301 let (history_system, history) = split_system_messages_from_history(history);
302
303 let turns = history
304 .into_iter()
305 .map(Turn::try_from)
306 .collect::<Result<Vec<_>, _>>()
307 .map_err(|err| CompletionError::RequestError(Box::new(err)))?;
308
309 let input = InteractionInput::Turns(turns);
310
311 let raw_params = completion_request
312 .additional_params
313 .unwrap_or_else(|| Value::Object(Map::new()));
314
315 let mut params: AdditionalParameters = serde_json::from_value(raw_params)?;
316
317 let mut generation_config = params.generation_config.take().unwrap_or_default();
318 if let Some(temp) = completion_request.temperature {
319 generation_config.temperature = Some(temp);
320 }
321 if let Some(max_tokens) = completion_request.max_tokens {
322 generation_config.max_output_tokens = Some(max_tokens);
323 }
324 if let Some(tool_choice) = completion_request.tool_choice {
325 generation_config.tool_choice = Some(tool_choice.try_into()?);
326 }
327 let generation_config = if generation_config.is_empty() {
328 None
329 } else {
330 Some(generation_config)
331 };
332
333 let system_instruction = completion_request
334 .preamble
335 .or_else(|| {
336 if history_system.is_empty() {
337 None
338 } else {
339 Some(history_system.join("\n\n"))
340 }
341 })
342 .or(params.system_instruction.take());
343
344 let mut tools = Vec::new();
345 if !completion_request.tools.is_empty() {
346 tools.extend(
347 completion_request
348 .tools
349 .into_iter()
350 .map(Tool::try_from)
351 .collect::<Result<Vec<_>, _>>()?,
352 );
353 }
354 if let Some(mut extra_tools) = params.tools.take() {
355 tools.append(&mut extra_tools);
356 }
357 let tools = if tools.is_empty() { None } else { Some(tools) };
358
359 let stream = stream_override.or(params.stream.take());
360
361 let (agent, agent_config) = if params.agent.is_some() {
362 (params.agent.take(), params.agent_config.take())
363 } else {
364 (None, None)
365 };
366
367 let response_format = params.response_format.take();
368 let response_mime_type = params.response_mime_type.take();
369
370 if response_format.is_some() && response_mime_type.is_none() {
371 return Err(CompletionError::RequestError(Box::new(
372 std::io::Error::new(
373 std::io::ErrorKind::InvalidInput,
374 "response_mime_type is required when response_format is set",
375 ),
376 )));
377 }
378
379 Ok(CreateInteractionRequest {
380 model: if agent.is_some() { None } else { Some(model) },
381 agent,
382 input,
383 system_instruction,
384 tools,
385 response_format,
386 response_mime_type,
387 stream,
388 store: params.store.take(),
389 background: params.background.take(),
390 generation_config,
391 agent_config,
392 response_modalities: params.response_modalities.take(),
393 previous_interaction_id: params.previous_interaction_id.take(),
394 additional_params: params.additional_params.take(),
395 })
396}
397
398fn split_system_messages_from_history(
399 history: Vec<completion::Message>,
400) -> (Vec<String>, Vec<completion::Message>) {
401 let mut system = Vec::new();
402 let mut remaining = Vec::new();
403
404 for message in history {
405 match message {
406 completion::Message::System { content } => system.push(content),
407 other => remaining.push(other),
408 }
409 }
410
411 (system, remaining)
412}
413
414async fn send_interaction_request<T>(
415 client: &InteractionsClient<T>,
416 request: crate::http_client::Request<Vec<u8>>,
417) -> Result<Interaction, CompletionError>
418where
419 T: HttpClientExt + Clone + std::fmt::Debug + Default + 'static,
420{
421 let response = client.send::<_, Vec<u8>>(request).await?;
422
423 if response.status().is_success() {
424 let response_body = response
425 .into_body()
426 .await
427 .map_err(CompletionError::HttpError)?;
428
429 let response_text = String::from_utf8_lossy(&response_body).to_string();
430
431 let response: Interaction = serde_json::from_slice(&response_body).map_err(|err| {
432 tracing::error!(
433 error = %err,
434 body = %response_text,
435 "Failed to deserialize Gemini interactions response"
436 );
437 CompletionError::JsonError(err)
438 })?;
439
440 Ok(response)
441 } else {
442 let text = String::from_utf8_lossy(
443 &response
444 .into_body()
445 .await
446 .map_err(CompletionError::HttpError)?,
447 )
448 .into();
449
450 Err(CompletionError::ProviderError(text))
451 }
452}
453
454fn build_interaction_stream_path(interaction_id: &str, last_event_id: Option<&str>) -> String {
455 let mut serializer = form_urlencoded::Serializer::new(String::new());
456 serializer.append_pair("stream", "true");
457 if let Some(last_event_id) = last_event_id {
458 serializer.append_pair("last_event_id", last_event_id);
459 }
460 format!(
461 "/v1beta/interactions/{}?{}",
462 interaction_id,
463 serializer.finish()
464 )
465}
466
467impl TryFrom<Interaction> for completion::CompletionResponse<Interaction> {
468 type Error = CompletionError;
469
470 fn try_from(response: Interaction) -> Result<Self, Self::Error> {
471 if response.outputs.is_empty() {
472 let status = response.status.as_ref().map(|status| format!("{status:?}"));
473 let message = match status {
474 Some(status) => format!(
475 "Interaction contained no outputs (status: {status}). Use get_interaction for background tasks."
476 ),
477 None => "Interaction contained no outputs".to_string(),
478 };
479 return Err(CompletionError::ResponseError(message));
480 }
481
482 let content = response
483 .outputs
484 .iter()
485 .cloned()
486 .filter_map(|output| match assistant_content_from_output(output) {
487 Ok(Some(content)) => Some(Ok(content)),
488 Ok(None) => None,
489 Err(err) => Some(Err(err)),
490 })
491 .collect::<Result<Vec<_>, _>>()?;
492
493 let choice = OneOrMany::many(content).map_err(|_| {
494 CompletionError::ResponseError(
495 "Response contained no message or tool call (empty)".to_owned(),
496 )
497 })?;
498
499 let usage = response
500 .usage
501 .as_ref()
502 .and_then(|usage| usage.token_usage())
503 .unwrap_or_default();
504
505 Ok(completion::CompletionResponse {
506 choice,
507 usage,
508 raw_response: response,
509 message_id: None,
510 })
511 }
512}
513
514fn assistant_content_from_output(
515 output: Content,
516) -> Result<Option<completion::AssistantContent>, CompletionError> {
517 match output {
518 Content::Text(TextContent { text, .. }) => {
519 Ok(Some(completion::AssistantContent::text(text)))
520 }
521 Content::FunctionCall(FunctionCallContent {
522 name,
523 arguments,
524 id,
525 ..
526 }) => {
527 let Some(name) = name else {
528 return Ok(None);
529 };
530 let call_id = id.unwrap_or_else(|| name.clone());
531 Ok(Some(completion::AssistantContent::tool_call_with_call_id(
532 name.clone(),
533 call_id,
534 name,
535 arguments.unwrap_or(Value::Object(Map::new())),
536 )))
537 }
538 Content::Thought(ThoughtContent {
539 summary, signature, ..
540 }) => {
541 let mut reasoning_content = summary
542 .unwrap_or_default()
543 .into_iter()
544 .filter_map(|content| match content {
545 ThoughtSummaryContent::Text(text) => Some(message::ReasoningContent::Text {
546 text: text.text,
547 signature: None,
548 }),
549 _ => None,
550 })
551 .collect::<Vec<_>>();
552
553 if reasoning_content.is_empty() {
554 return Ok(None);
555 }
556
557 if let Some(signature) = signature
558 && let Some(message::ReasoningContent::Text {
559 signature: first_signature,
560 ..
561 }) = reasoning_content
562 .iter_mut()
563 .find(|content| matches!(content, message::ReasoningContent::Text { .. }))
564 {
565 *first_signature = Some(signature);
566 }
567
568 Ok(Some(completion::AssistantContent::Reasoning(Reasoning {
569 id: None,
570 content: reasoning_content,
571 })))
572 }
573 Content::Image(ImageContent {
574 data,
575 uri,
576 mime_type,
577 ..
578 }) => {
579 let Some(mime_type) = mime_type else {
580 return Err(CompletionError::ResponseError(
581 "Image output missing mime_type".to_owned(),
582 ));
583 };
584
585 let media_type =
586 message::ImageMediaType::from_mime_type(&mime_type).ok_or_else(|| {
587 CompletionError::ResponseError(format!(
588 "Unsupported image output mime type {mime_type}"
589 ))
590 })?;
591
592 let image = if let Some(data) = data {
593 message::AssistantContent::image_base64(
594 data,
595 Some(media_type),
596 Some(message::ImageDetail::default()),
597 )
598 } else if let Some(uri) = uri {
599 completion::AssistantContent::Image(message::Image {
600 data: message::DocumentSourceKind::Url(uri),
601 media_type: Some(media_type),
602 detail: Some(message::ImageDetail::default()),
603 additional_params: None,
604 })
605 } else {
606 return Err(CompletionError::ResponseError(
607 "Image output missing data or uri".to_owned(),
608 ));
609 };
610
611 Ok(Some(image))
612 }
613 _ => Ok(None),
614 }
615}
616
617fn split_data_uri(
618 src: message::DocumentSourceKind,
619) -> Result<(Option<String>, Option<String>), message::MessageError> {
620 match src {
621 message::DocumentSourceKind::Url(uri) => Ok((None, Some(uri))),
622 message::DocumentSourceKind::Base64(data) | message::DocumentSourceKind::String(data) => {
623 Ok((Some(data), None))
624 }
625 message::DocumentSourceKind::Raw(_) => Err(message::MessageError::ConversionError(
626 "Raw content is not supported, encode as base64 first".to_string(),
627 )),
628 message::DocumentSourceKind::Unknown => Err(message::MessageError::ConversionError(
629 "Unknown content source".to_string(),
630 )),
631 }
632}
633
634pub mod interactions_api_types {
636 use super::split_data_uri;
637 use crate::completion::{CompletionError, GetTokenUsage, Usage};
638 use crate::message::{self, MimeType};
639 use crate::telemetry::ProviderResponseExt;
640 use serde::{Deserialize, Serialize};
641 use serde_json::{Value, json};
642
643 #[derive(Debug, Deserialize, Serialize, Default, Clone)]
649 #[serde(rename_all = "snake_case")]
650 pub struct AdditionalParameters {
651 pub agent: Option<String>,
652 pub agent_config: Option<AgentConfig>,
653 pub background: Option<bool>,
654 pub generation_config: Option<GenerationConfig>,
655 pub previous_interaction_id: Option<String>,
656 pub response_modalities: Option<Vec<ResponseModality>>,
657 pub response_format: Option<Value>,
658 pub response_mime_type: Option<String>,
659 pub store: Option<bool>,
660 pub stream: Option<bool>,
661 pub system_instruction: Option<String>,
662 pub tools: Option<Vec<Tool>>,
663 #[serde(flatten, skip_serializing_if = "Option::is_none")]
664 pub additional_params: Option<Value>,
665 }
666
667 #[derive(Debug, Deserialize, Serialize, Clone)]
669 #[serde(rename_all = "snake_case")]
670 pub struct CreateInteractionRequest {
671 #[serde(skip_serializing_if = "Option::is_none")]
672 pub model: Option<String>,
673 #[serde(skip_serializing_if = "Option::is_none")]
674 pub agent: Option<String>,
675 pub input: InteractionInput,
676 #[serde(skip_serializing_if = "Option::is_none")]
677 pub system_instruction: Option<String>,
678 #[serde(skip_serializing_if = "Option::is_none")]
679 pub tools: Option<Vec<Tool>>,
680 #[serde(skip_serializing_if = "Option::is_none")]
681 pub response_format: Option<Value>,
682 #[serde(skip_serializing_if = "Option::is_none")]
683 pub response_mime_type: Option<String>,
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub stream: Option<bool>,
686 #[serde(skip_serializing_if = "Option::is_none")]
687 pub store: Option<bool>,
688 #[serde(skip_serializing_if = "Option::is_none")]
689 pub background: Option<bool>,
690 #[serde(skip_serializing_if = "Option::is_none")]
691 pub generation_config: Option<GenerationConfig>,
692 #[serde(skip_serializing_if = "Option::is_none")]
693 pub agent_config: Option<AgentConfig>,
694 #[serde(skip_serializing_if = "Option::is_none")]
695 pub response_modalities: Option<Vec<ResponseModality>>,
696 #[serde(skip_serializing_if = "Option::is_none")]
697 pub previous_interaction_id: Option<String>,
698 #[serde(flatten, skip_serializing_if = "Option::is_none")]
699 pub additional_params: Option<Value>,
700 }
701
702 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
704 #[serde(rename_all = "snake_case")]
705 pub struct Interaction {
706 #[serde(default)]
707 pub id: String,
708 #[serde(skip_serializing_if = "Option::is_none")]
709 pub model: Option<String>,
710 #[serde(skip_serializing_if = "Option::is_none")]
711 pub agent: Option<String>,
712 #[serde(skip_serializing_if = "Option::is_none")]
713 pub status: Option<InteractionStatus>,
714 #[serde(skip_serializing_if = "Option::is_none")]
715 pub object: Option<String>,
716 #[serde(skip_serializing_if = "Option::is_none")]
717 pub created: Option<String>,
718 #[serde(skip_serializing_if = "Option::is_none")]
719 pub updated: Option<String>,
720 #[serde(skip_serializing_if = "Option::is_none")]
721 pub role: Option<String>,
722 #[serde(default)]
723 pub outputs: Vec<Content>,
724 #[serde(skip_serializing_if = "Option::is_none")]
725 pub usage: Option<InteractionUsage>,
726 #[serde(skip_serializing_if = "Option::is_none")]
727 pub system_instruction: Option<String>,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 pub tools: Option<Vec<Tool>>,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub background: Option<bool>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub response_modalities: Option<Vec<ResponseModality>>,
734 #[serde(skip_serializing_if = "Option::is_none")]
735 pub response_format: Option<Value>,
736 #[serde(skip_serializing_if = "Option::is_none")]
737 pub response_mime_type: Option<String>,
738 #[serde(skip_serializing_if = "Option::is_none")]
739 pub previous_interaction_id: Option<String>,
740 #[serde(skip_serializing_if = "Option::is_none")]
741 pub input: Option<InteractionInput>,
742 }
743
744 impl GetTokenUsage for Interaction {
745 fn token_usage(&self) -> Option<Usage> {
746 self.usage.as_ref().and_then(|usage| usage.token_usage())
747 }
748 }
749
750 impl ProviderResponseExt for Interaction {
751 type OutputMessage = Content;
752 type Usage = InteractionUsage;
753
754 fn get_response_id(&self) -> Option<String> {
755 if self.id.is_empty() {
756 None
757 } else {
758 Some(self.id.clone())
759 }
760 }
761
762 fn get_response_model_name(&self) -> Option<String> {
763 self.model.clone()
764 }
765
766 fn get_output_messages(&self) -> Vec<Self::OutputMessage> {
767 self.outputs.clone()
768 }
769
770 fn get_text_response(&self) -> Option<String> {
771 let text = self
772 .outputs
773 .iter()
774 .filter_map(|content| match content {
775 Content::Text(text) => Some(text.text.clone()),
776 _ => None,
777 })
778 .collect::<Vec<_>>()
779 .join("\n");
780
781 if text.is_empty() { None } else { Some(text) }
782 }
783
784 fn get_usage(&self) -> Option<Self::Usage> {
785 self.usage.clone()
786 }
787 }
788
789 #[derive(Clone, Debug, Default)]
791 pub struct GoogleSearchExchange {
792 pub call_id: Option<String>,
794 pub calls: Vec<GoogleSearchCallContent>,
796 pub results: Vec<GoogleSearchResultContent>,
798 }
799
800 impl GoogleSearchExchange {
801 pub fn queries(&self) -> Vec<String> {
803 let mut queries = Vec::new();
804 for call in &self.calls {
805 if let Some(args) = &call.arguments
806 && let Some(call_queries) = &args.queries
807 {
808 queries.extend(call_queries.clone());
809 }
810 }
811 queries
812 }
813
814 pub fn result_items(&self) -> Vec<GoogleSearchResult> {
816 let mut items = Vec::new();
817 for result in &self.results {
818 if let Some(entries) = &result.result {
819 items.extend(entries.clone());
820 }
821 }
822 items
823 }
824 }
825
826 #[derive(Clone, Debug, Default)]
828 pub struct UrlContextExchange {
829 pub call_id: Option<String>,
831 pub calls: Vec<UrlContextCallContent>,
833 pub results: Vec<UrlContextResultContent>,
835 }
836
837 impl UrlContextExchange {
838 pub fn urls(&self) -> Vec<String> {
840 let mut urls = Vec::new();
841 for call in &self.calls {
842 if let Some(args) = &call.arguments
843 && let Some(call_urls) = &args.urls
844 {
845 urls.extend(call_urls.clone());
846 }
847 }
848 urls
849 }
850
851 pub fn result_items(&self) -> Vec<UrlContextResult> {
853 let mut items = Vec::new();
854 for result in &self.results {
855 if let Some(entries) = &result.result {
856 items.extend(entries.clone());
857 }
858 }
859 items
860 }
861 }
862
863 #[derive(Clone, Debug, Default)]
865 pub struct CodeExecutionExchange {
866 pub call_id: Option<String>,
868 pub calls: Vec<CodeExecutionCallContent>,
870 pub results: Vec<CodeExecutionResultContent>,
872 }
873
874 impl CodeExecutionExchange {
875 pub fn code_snippets(&self) -> Vec<String> {
877 let mut snippets = Vec::new();
878 for call in &self.calls {
879 if let Some(args) = &call.arguments
880 && let Some(code) = &args.code
881 {
882 snippets.push(code.clone());
883 }
884 }
885 snippets
886 }
887
888 pub fn outputs(&self) -> Vec<String> {
890 let mut outputs = Vec::new();
891 for result in &self.results {
892 if let Some(output) = &result.result {
893 outputs.push(output.clone());
894 }
895 }
896 outputs
897 }
898 }
899
900 impl Interaction {
901 pub fn google_search_exchanges(&self) -> Vec<GoogleSearchExchange> {
906 let mut exchanges: Vec<GoogleSearchExchange> = Vec::new();
907 let mut last_call_index: Option<usize> = None;
908
909 for content in &self.outputs {
910 match content {
911 Content::GoogleSearchCall(call) => {
912 let index = if let Some(call_id) = call.id.as_ref() {
913 if let Some(index) = exchanges
914 .iter()
915 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
916 {
917 exchanges[index].calls.push(call.clone());
918 index
919 } else {
920 exchanges.push(GoogleSearchExchange {
921 call_id: Some(call_id.clone()),
922 calls: vec![call.clone()],
923 results: Vec::new(),
924 });
925 exchanges.len() - 1
926 }
927 } else {
928 exchanges.push(GoogleSearchExchange {
929 call_id: None,
930 calls: vec![call.clone()],
931 results: Vec::new(),
932 });
933 exchanges.len() - 1
934 };
935 last_call_index = Some(index);
936 }
937 Content::GoogleSearchResult(result) => {
938 if let Some(call_id) = result.call_id.as_ref() {
939 if let Some(index) = exchanges
940 .iter()
941 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
942 {
943 exchanges[index].results.push(result.clone());
944 } else {
945 exchanges.push(GoogleSearchExchange {
946 call_id: Some(call_id.clone()),
947 calls: Vec::new(),
948 results: vec![result.clone()],
949 });
950 }
951 } else if let Some(index) = last_call_index {
952 exchanges[index].results.push(result.clone());
953 } else {
954 exchanges.push(GoogleSearchExchange {
955 call_id: None,
956 calls: Vec::new(),
957 results: vec![result.clone()],
958 });
959 last_call_index = Some(exchanges.len() - 1);
960 }
961 }
962 _ => {}
963 }
964 }
965
966 exchanges
967 }
968
969 pub fn google_search_call_contents(&self) -> Vec<GoogleSearchCallContent> {
971 self.google_search_exchanges()
972 .into_iter()
973 .flat_map(|exchange| exchange.calls)
974 .collect()
975 }
976
977 pub fn google_search_result_contents(&self) -> Vec<GoogleSearchResultContent> {
979 self.google_search_exchanges()
980 .into_iter()
981 .flat_map(|exchange| exchange.results)
982 .collect()
983 }
984
985 pub fn google_search_queries(&self) -> Vec<String> {
987 self.google_search_exchanges()
988 .into_iter()
989 .flat_map(|exchange| exchange.queries())
990 .collect()
991 }
992
993 pub fn google_search_results(&self) -> Vec<GoogleSearchResult> {
995 self.google_search_exchanges()
996 .into_iter()
997 .flat_map(|exchange| exchange.result_items())
998 .collect()
999 }
1000
1001 pub fn url_context_exchanges(&self) -> Vec<UrlContextExchange> {
1006 let mut exchanges: Vec<UrlContextExchange> = Vec::new();
1007 let mut last_call_index: Option<usize> = None;
1008
1009 for content in &self.outputs {
1010 match content {
1011 Content::UrlContextCall(call) => {
1012 let index = if let Some(call_id) = call.id.as_ref() {
1013 if let Some(index) = exchanges
1014 .iter()
1015 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
1016 {
1017 exchanges[index].calls.push(call.clone());
1018 index
1019 } else {
1020 exchanges.push(UrlContextExchange {
1021 call_id: Some(call_id.clone()),
1022 calls: vec![call.clone()],
1023 results: Vec::new(),
1024 });
1025 exchanges.len() - 1
1026 }
1027 } else {
1028 exchanges.push(UrlContextExchange {
1029 call_id: None,
1030 calls: vec![call.clone()],
1031 results: Vec::new(),
1032 });
1033 exchanges.len() - 1
1034 };
1035 last_call_index = Some(index);
1036 }
1037 Content::UrlContextResult(result) => {
1038 if let Some(call_id) = result.call_id.as_ref() {
1039 if let Some(index) = exchanges
1040 .iter()
1041 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
1042 {
1043 exchanges[index].results.push(result.clone());
1044 } else {
1045 exchanges.push(UrlContextExchange {
1046 call_id: Some(call_id.clone()),
1047 calls: Vec::new(),
1048 results: vec![result.clone()],
1049 });
1050 }
1051 } else if let Some(index) = last_call_index {
1052 exchanges[index].results.push(result.clone());
1053 } else {
1054 exchanges.push(UrlContextExchange {
1055 call_id: None,
1056 calls: Vec::new(),
1057 results: vec![result.clone()],
1058 });
1059 last_call_index = Some(exchanges.len() - 1);
1060 }
1061 }
1062 _ => {}
1063 }
1064 }
1065
1066 exchanges
1067 }
1068
1069 pub fn url_context_call_contents(&self) -> Vec<UrlContextCallContent> {
1071 self.url_context_exchanges()
1072 .into_iter()
1073 .flat_map(|exchange| exchange.calls)
1074 .collect()
1075 }
1076
1077 pub fn url_context_result_contents(&self) -> Vec<UrlContextResultContent> {
1079 self.url_context_exchanges()
1080 .into_iter()
1081 .flat_map(|exchange| exchange.results)
1082 .collect()
1083 }
1084
1085 pub fn url_context_urls(&self) -> Vec<String> {
1087 self.url_context_exchanges()
1088 .into_iter()
1089 .flat_map(|exchange| exchange.urls())
1090 .collect()
1091 }
1092
1093 pub fn url_context_results(&self) -> Vec<UrlContextResult> {
1095 self.url_context_exchanges()
1096 .into_iter()
1097 .flat_map(|exchange| exchange.result_items())
1098 .collect()
1099 }
1100
1101 pub fn code_execution_exchanges(&self) -> Vec<CodeExecutionExchange> {
1106 let mut exchanges: Vec<CodeExecutionExchange> = Vec::new();
1107 let mut last_call_index: Option<usize> = None;
1108
1109 for content in &self.outputs {
1110 match content {
1111 Content::CodeExecutionCall(call) => {
1112 let index = if let Some(call_id) = call.id.as_ref() {
1113 if let Some(index) = exchanges
1114 .iter()
1115 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
1116 {
1117 exchanges[index].calls.push(call.clone());
1118 index
1119 } else {
1120 exchanges.push(CodeExecutionExchange {
1121 call_id: Some(call_id.clone()),
1122 calls: vec![call.clone()],
1123 results: Vec::new(),
1124 });
1125 exchanges.len() - 1
1126 }
1127 } else {
1128 exchanges.push(CodeExecutionExchange {
1129 call_id: None,
1130 calls: vec![call.clone()],
1131 results: Vec::new(),
1132 });
1133 exchanges.len() - 1
1134 };
1135 last_call_index = Some(index);
1136 }
1137 Content::CodeExecutionResult(result) => {
1138 if let Some(call_id) = result.call_id.as_ref() {
1139 if let Some(index) = exchanges
1140 .iter()
1141 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
1142 {
1143 exchanges[index].results.push(result.clone());
1144 } else {
1145 exchanges.push(CodeExecutionExchange {
1146 call_id: Some(call_id.clone()),
1147 calls: Vec::new(),
1148 results: vec![result.clone()],
1149 });
1150 }
1151 } else if let Some(index) = last_call_index {
1152 exchanges[index].results.push(result.clone());
1153 } else {
1154 exchanges.push(CodeExecutionExchange {
1155 call_id: None,
1156 calls: Vec::new(),
1157 results: vec![result.clone()],
1158 });
1159 last_call_index = Some(exchanges.len() - 1);
1160 }
1161 }
1162 _ => {}
1163 }
1164 }
1165
1166 exchanges
1167 }
1168
1169 pub fn code_execution_call_contents(&self) -> Vec<CodeExecutionCallContent> {
1171 self.code_execution_exchanges()
1172 .into_iter()
1173 .flat_map(|exchange| exchange.calls)
1174 .collect()
1175 }
1176
1177 pub fn code_execution_result_contents(&self) -> Vec<CodeExecutionResultContent> {
1179 self.code_execution_exchanges()
1180 .into_iter()
1181 .flat_map(|exchange| exchange.results)
1182 .collect()
1183 }
1184
1185 pub fn code_execution_snippets(&self) -> Vec<String> {
1187 self.code_execution_exchanges()
1188 .into_iter()
1189 .flat_map(|exchange| exchange.code_snippets())
1190 .collect()
1191 }
1192
1193 pub fn code_execution_outputs(&self) -> Vec<String> {
1195 self.code_execution_exchanges()
1196 .into_iter()
1197 .flat_map(|exchange| exchange.outputs())
1198 .collect()
1199 }
1200
1201 pub fn text_with_inline_citations(&self) -> Option<String> {
1203 let text = self
1204 .outputs
1205 .iter()
1206 .filter_map(|content| match content {
1207 Content::Text(text) => Some(text.with_inline_citations()),
1208 _ => None,
1209 })
1210 .collect::<Vec<_>>()
1211 .join("\n");
1212
1213 if text.is_empty() { None } else { Some(text) }
1214 }
1215
1216 pub fn is_terminal(&self) -> bool {
1218 self.status
1219 .as_ref()
1220 .is_some_and(InteractionStatus::is_terminal)
1221 }
1222
1223 pub fn is_completed(&self) -> bool {
1225 matches!(self.status, Some(InteractionStatus::Completed))
1226 }
1227 }
1228
1229 #[derive(Clone, Debug, Deserialize, Serialize)]
1231 #[serde(rename_all = "snake_case")]
1232 pub enum InteractionStatus {
1233 InProgress,
1234 RequiresAction,
1235 Completed,
1236 Failed,
1237 Cancelled,
1238 }
1239
1240 impl InteractionStatus {
1241 pub fn is_terminal(&self) -> bool {
1243 matches!(
1244 self,
1245 InteractionStatus::Completed
1246 | InteractionStatus::Failed
1247 | InteractionStatus::Cancelled
1248 )
1249 }
1250 }
1251
1252 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
1254 #[serde(rename_all = "snake_case")]
1255 pub struct InteractionUsage {
1256 #[serde(skip_serializing_if = "Option::is_none")]
1257 pub total_input_tokens: Option<u64>,
1258 #[serde(skip_serializing_if = "Option::is_none")]
1259 pub total_output_tokens: Option<u64>,
1260 #[serde(skip_serializing_if = "Option::is_none")]
1261 pub total_tokens: Option<u64>,
1262 }
1263
1264 impl GetTokenUsage for InteractionUsage {
1265 fn token_usage(&self) -> Option<Usage> {
1266 let mut usage = Usage::new();
1267 usage.input_tokens = self.total_input_tokens.unwrap_or_default();
1268 usage.output_tokens = self.total_output_tokens.unwrap_or_default();
1269 usage.total_tokens = self
1270 .total_tokens
1271 .unwrap_or(usage.input_tokens + usage.output_tokens);
1272 Some(usage)
1273 }
1274 }
1275
1276 #[derive(Clone, Debug, Deserialize, Serialize)]
1278 #[serde(untagged)]
1279 pub enum InteractionInput {
1280 Text(String),
1281 Content(Content),
1282 Turns(Vec<Turn>),
1283 Contents(Vec<Content>),
1284 }
1285
1286 #[derive(Clone, Debug, Deserialize, Serialize)]
1288 #[serde(rename_all = "lowercase")]
1289 pub enum Role {
1290 User,
1291 Model,
1292 }
1293
1294 #[derive(Clone, Debug, Deserialize, Serialize)]
1296 pub struct Turn {
1297 pub role: Role,
1298 pub content: TurnContent,
1299 }
1300
1301 #[derive(Clone, Debug, Deserialize, Serialize)]
1303 #[serde(untagged)]
1304 pub enum TurnContent {
1305 Text(String),
1306 Contents(Vec<Content>),
1307 }
1308
1309 impl TryFrom<crate::completion::Message> for Turn {
1310 type Error = message::MessageError;
1311
1312 fn try_from(message: crate::completion::Message) -> Result<Self, Self::Error> {
1313 match message {
1314 crate::completion::Message::System { content } => Ok(Self {
1315 role: Role::User,
1316 content: TurnContent::Text(content),
1317 }),
1318 crate::completion::Message::User { content } => {
1319 let contents = content
1320 .into_iter()
1321 .map(Content::try_from)
1322 .collect::<Result<Vec<_>, _>>()?;
1323 Ok(Self {
1324 role: Role::User,
1325 content: TurnContent::Contents(contents),
1326 })
1327 }
1328 crate::completion::Message::Assistant { content, .. } => {
1329 let contents = content
1330 .into_iter()
1331 .map(Content::try_from)
1332 .collect::<Result<Vec<_>, _>>()?;
1333 Ok(Self {
1334 role: Role::Model,
1335 content: TurnContent::Contents(contents),
1336 })
1337 }
1338 }
1339 }
1340 }
1341
1342 #[derive(Clone, Debug, Deserialize, Serialize)]
1348 pub struct Annotation {
1349 #[serde(skip_serializing_if = "Option::is_none")]
1350 pub start_index: Option<i64>,
1351 #[serde(skip_serializing_if = "Option::is_none")]
1352 pub end_index: Option<i64>,
1353 #[serde(skip_serializing_if = "Option::is_none")]
1354 pub source: Option<String>,
1355 }
1356
1357 #[derive(Clone, Debug)]
1359 pub struct Citation {
1360 pub start_index: usize,
1361 pub end_index: usize,
1362 pub source: String,
1363 }
1364
1365 #[derive(Clone, Debug, Deserialize, Serialize)]
1367 pub struct TextContent {
1368 pub text: String,
1369 #[serde(skip_serializing_if = "Option::is_none")]
1370 pub annotations: Option<Vec<Annotation>>,
1371 }
1372
1373 impl TextContent {
1374 pub fn citations(&self) -> Vec<Citation> {
1376 let mut citations = Vec::new();
1377 let Some(annotations) = self.annotations.as_ref() else {
1378 return citations;
1379 };
1380
1381 for annotation in annotations {
1382 let (Some(start), Some(end), Some(source)) = (
1383 annotation.start_index,
1384 annotation.end_index,
1385 annotation.source.as_ref(),
1386 ) else {
1387 continue;
1388 };
1389
1390 if start < 0 || end < 0 {
1391 continue;
1392 }
1393 let start = start as usize;
1394 let end = end as usize;
1395 if end <= start || end > self.text.len() {
1396 continue;
1397 }
1398 if !self.text.is_char_boundary(start) || !self.text.is_char_boundary(end) {
1399 continue;
1400 }
1401
1402 citations.push(Citation {
1403 start_index: start,
1404 end_index: end,
1405 source: source.clone(),
1406 });
1407 }
1408
1409 citations.sort_by(|a, b| {
1410 a.start_index
1411 .cmp(&b.start_index)
1412 .then_with(|| a.end_index.cmp(&b.end_index))
1413 });
1414
1415 citations
1416 }
1417
1418 pub fn with_inline_citations(&self) -> String {
1420 let citations = self.citations();
1421 if citations.is_empty() {
1422 return self.text.clone();
1423 }
1424
1425 let mut source_order = Vec::new();
1426 for citation in &citations {
1427 if !source_order.contains(&citation.source) {
1428 source_order.push(citation.source.clone());
1429 }
1430 }
1431
1432 let mut inserts = citations
1433 .iter()
1434 .map(|citation| {
1435 let index = source_order
1436 .iter()
1437 .position(|source| source == &citation.source)
1438 .map(|idx| idx + 1)
1439 .unwrap_or(0);
1440 (
1441 citation.start_index,
1442 citation.end_index,
1443 index,
1444 &citation.source,
1445 )
1446 })
1447 .collect::<Vec<_>>();
1448
1449 inserts.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| b.0.cmp(&a.0)));
1450
1451 let mut text = self.text.clone();
1452 for (_, end, index, source) in inserts {
1453 if index == 0 {
1454 continue;
1455 }
1456 let citation = format!("[{}]({})", index, source);
1457 text.insert_str(end, &citation);
1458 }
1459
1460 text
1461 }
1462 }
1463
1464 #[derive(Clone, Debug, Deserialize, Serialize)]
1466 pub struct ImageContent {
1467 #[serde(skip_serializing_if = "Option::is_none")]
1468 pub data: Option<String>,
1469 #[serde(skip_serializing_if = "Option::is_none")]
1470 pub uri: Option<String>,
1471 #[serde(skip_serializing_if = "Option::is_none")]
1472 pub mime_type: Option<String>,
1473 #[serde(skip_serializing_if = "Option::is_none")]
1474 pub resolution: Option<MediaResolution>,
1475 }
1476
1477 #[derive(Clone, Debug, Deserialize, Serialize)]
1479 pub struct AudioContent {
1480 #[serde(skip_serializing_if = "Option::is_none")]
1481 pub data: Option<String>,
1482 #[serde(skip_serializing_if = "Option::is_none")]
1483 pub uri: Option<String>,
1484 #[serde(skip_serializing_if = "Option::is_none")]
1485 pub mime_type: Option<String>,
1486 }
1487
1488 #[derive(Clone, Debug, Deserialize, Serialize)]
1490 pub struct DocumentContent {
1491 #[serde(skip_serializing_if = "Option::is_none")]
1492 pub data: Option<String>,
1493 #[serde(skip_serializing_if = "Option::is_none")]
1494 pub uri: Option<String>,
1495 #[serde(skip_serializing_if = "Option::is_none")]
1496 pub mime_type: Option<String>,
1497 }
1498
1499 #[derive(Clone, Debug, Deserialize, Serialize)]
1501 pub struct VideoContent {
1502 #[serde(skip_serializing_if = "Option::is_none")]
1503 pub data: Option<String>,
1504 #[serde(skip_serializing_if = "Option::is_none")]
1505 pub uri: Option<String>,
1506 #[serde(skip_serializing_if = "Option::is_none")]
1507 pub mime_type: Option<String>,
1508 #[serde(skip_serializing_if = "Option::is_none")]
1509 pub resolution: Option<MediaResolution>,
1510 }
1511
1512 #[derive(Clone, Debug, Deserialize, Serialize)]
1514 pub struct ThoughtContent {
1515 #[serde(skip_serializing_if = "Option::is_none")]
1516 pub signature: Option<String>,
1517 #[serde(skip_serializing_if = "Option::is_none")]
1518 pub summary: Option<Vec<ThoughtSummaryContent>>,
1519 }
1520
1521 #[derive(Clone, Debug, Deserialize, Serialize)]
1523 #[serde(untagged)]
1524 pub enum ThoughtSummaryContent {
1525 Text(TextContent),
1526 Image(ImageContent),
1527 }
1528
1529 #[derive(Clone, Debug, Deserialize, Serialize)]
1531 pub struct FunctionCallContent {
1532 #[serde(skip_serializing_if = "Option::is_none")]
1533 pub name: Option<String>,
1534 #[serde(skip_serializing_if = "Option::is_none")]
1535 pub arguments: Option<Value>,
1536 #[serde(skip_serializing_if = "Option::is_none")]
1537 pub id: Option<String>,
1538 }
1539
1540 #[derive(Clone, Debug, Deserialize, Serialize)]
1542 pub struct FunctionResultContent {
1543 #[serde(skip_serializing_if = "Option::is_none")]
1544 pub name: Option<String>,
1545 #[serde(skip_serializing_if = "Option::is_none")]
1546 pub is_error: Option<bool>,
1547 #[serde(skip_serializing_if = "Option::is_none")]
1548 pub result: Option<Value>,
1549 #[serde(skip_serializing_if = "Option::is_none")]
1550 pub call_id: Option<String>,
1551 }
1552
1553 #[derive(Clone, Debug, Deserialize, Serialize)]
1555 pub struct CodeExecutionCallArguments {
1556 #[serde(skip_serializing_if = "Option::is_none")]
1557 pub language: Option<String>,
1558 #[serde(skip_serializing_if = "Option::is_none")]
1559 pub code: Option<String>,
1560 }
1561
1562 #[derive(Clone, Debug, Deserialize, Serialize)]
1564 pub struct CodeExecutionCallContent {
1565 #[serde(skip_serializing_if = "Option::is_none")]
1566 pub arguments: Option<CodeExecutionCallArguments>,
1567 #[serde(skip_serializing_if = "Option::is_none")]
1568 pub id: Option<String>,
1569 }
1570
1571 #[derive(Clone, Debug, Deserialize, Serialize)]
1573 pub struct CodeExecutionResultContent {
1574 #[serde(skip_serializing_if = "Option::is_none")]
1575 pub result: Option<String>,
1576 #[serde(skip_serializing_if = "Option::is_none")]
1577 pub is_error: Option<bool>,
1578 #[serde(skip_serializing_if = "Option::is_none")]
1579 pub signature: Option<String>,
1580 #[serde(skip_serializing_if = "Option::is_none")]
1581 pub call_id: Option<String>,
1582 }
1583
1584 #[derive(Clone, Debug, Deserialize, Serialize)]
1586 pub struct UrlContextCallArguments {
1587 #[serde(skip_serializing_if = "Option::is_none")]
1588 pub urls: Option<Vec<String>>,
1589 }
1590
1591 #[derive(Clone, Debug, Deserialize, Serialize)]
1593 pub struct UrlContextCallContent {
1594 #[serde(skip_serializing_if = "Option::is_none")]
1595 pub arguments: Option<UrlContextCallArguments>,
1596 #[serde(skip_serializing_if = "Option::is_none")]
1597 pub id: Option<String>,
1598 }
1599
1600 #[derive(Clone, Debug, Deserialize, Serialize)]
1602 pub struct UrlContextResult {
1603 #[serde(skip_serializing_if = "Option::is_none")]
1604 pub url: Option<String>,
1605 #[serde(skip_serializing_if = "Option::is_none")]
1606 pub status: Option<String>,
1607 }
1608
1609 #[derive(Clone, Debug, Deserialize, Serialize)]
1611 pub struct UrlContextResultContent {
1612 #[serde(skip_serializing_if = "Option::is_none")]
1613 pub signature: Option<String>,
1614 #[serde(skip_serializing_if = "Option::is_none")]
1615 pub result: Option<Vec<UrlContextResult>>,
1616 #[serde(skip_serializing_if = "Option::is_none")]
1617 pub is_error: Option<bool>,
1618 #[serde(skip_serializing_if = "Option::is_none")]
1619 pub call_id: Option<String>,
1620 }
1621
1622 #[derive(Clone, Debug, Deserialize, Serialize)]
1624 pub struct GoogleSearchCallArguments {
1625 #[serde(skip_serializing_if = "Option::is_none")]
1626 pub queries: Option<Vec<String>>,
1627 }
1628
1629 #[derive(Clone, Debug, Deserialize, Serialize)]
1631 pub struct GoogleSearchCallContent {
1632 #[serde(skip_serializing_if = "Option::is_none")]
1633 pub arguments: Option<GoogleSearchCallArguments>,
1634 #[serde(skip_serializing_if = "Option::is_none")]
1635 pub id: Option<String>,
1636 }
1637
1638 #[derive(Clone, Debug, Deserialize, Serialize)]
1640 pub struct GoogleSearchResult {
1641 #[serde(skip_serializing_if = "Option::is_none")]
1642 pub url: Option<String>,
1643 #[serde(skip_serializing_if = "Option::is_none")]
1644 pub title: Option<String>,
1645 #[serde(skip_serializing_if = "Option::is_none")]
1646 pub rendered_content: Option<String>,
1647 }
1648
1649 #[derive(Clone, Debug, Deserialize, Serialize)]
1651 pub struct GoogleSearchResultContent {
1652 #[serde(skip_serializing_if = "Option::is_none")]
1653 pub signature: Option<String>,
1654 #[serde(skip_serializing_if = "Option::is_none")]
1655 pub result: Option<Vec<GoogleSearchResult>>,
1656 #[serde(skip_serializing_if = "Option::is_none")]
1657 pub is_error: Option<bool>,
1658 #[serde(skip_serializing_if = "Option::is_none")]
1659 pub call_id: Option<String>,
1660 }
1661
1662 #[derive(Clone, Debug, Deserialize, Serialize)]
1664 pub struct McpServerToolCallContent {
1665 #[serde(skip_serializing_if = "Option::is_none")]
1666 pub name: Option<String>,
1667 #[serde(skip_serializing_if = "Option::is_none")]
1668 pub server_name: Option<String>,
1669 #[serde(skip_serializing_if = "Option::is_none")]
1670 pub arguments: Option<Value>,
1671 #[serde(skip_serializing_if = "Option::is_none")]
1672 pub id: Option<String>,
1673 }
1674
1675 #[derive(Clone, Debug, Deserialize, Serialize)]
1677 pub struct McpServerToolResultContent {
1678 #[serde(skip_serializing_if = "Option::is_none")]
1679 pub name: Option<String>,
1680 #[serde(skip_serializing_if = "Option::is_none")]
1681 pub server_name: Option<String>,
1682 #[serde(skip_serializing_if = "Option::is_none")]
1683 pub result: Option<Value>,
1684 #[serde(skip_serializing_if = "Option::is_none")]
1685 pub call_id: Option<String>,
1686 }
1687
1688 #[derive(Clone, Debug, Deserialize, Serialize)]
1690 pub struct FileSearchResult {
1691 pub title: String,
1692 pub text: String,
1693 pub file_search_store: String,
1694 }
1695
1696 #[derive(Clone, Debug, Deserialize, Serialize)]
1698 pub struct FileSearchResultContent {
1699 #[serde(skip_serializing_if = "Option::is_none")]
1700 pub result: Option<Vec<FileSearchResult>>,
1701 }
1702
1703 #[derive(Clone, Debug, Deserialize, Serialize)]
1705 #[serde(tag = "type", rename_all = "snake_case")]
1706 pub enum Content {
1707 Text(TextContent),
1708 Image(ImageContent),
1709 Audio(AudioContent),
1710 Document(DocumentContent),
1711 Video(VideoContent),
1712 Thought(ThoughtContent),
1713 FunctionCall(FunctionCallContent),
1714 FunctionResult(FunctionResultContent),
1715 CodeExecutionCall(CodeExecutionCallContent),
1716 CodeExecutionResult(CodeExecutionResultContent),
1717 UrlContextCall(UrlContextCallContent),
1718 UrlContextResult(UrlContextResultContent),
1719 GoogleSearchCall(GoogleSearchCallContent),
1720 GoogleSearchResult(GoogleSearchResultContent),
1721 McpServerToolCall(McpServerToolCallContent),
1722 McpServerToolResult(McpServerToolResultContent),
1723 FileSearchResult(FileSearchResultContent),
1724 }
1725
1726 impl TryFrom<message::UserContent> for Content {
1727 type Error = message::MessageError;
1728
1729 fn try_from(content: message::UserContent) -> Result<Self, Self::Error> {
1730 match content {
1731 message::UserContent::Text(message::Text { text }) => Ok(Self::Text(TextContent {
1732 text,
1733 annotations: None,
1734 })),
1735 message::UserContent::ToolResult(message::ToolResult {
1736 id,
1737 call_id,
1738 content,
1739 }) => {
1740 let Some(call_id) = call_id else {
1741 return Err(message::MessageError::ConversionError(
1742 "Tool results require call_id for Gemini Interactions API".to_string(),
1743 ));
1744 };
1745
1746 let content = content.first();
1747
1748 let message::ToolResultContent::Text(text) = content else {
1749 return Err(message::MessageError::ConversionError(
1750 "Tool result content must be text".to_string(),
1751 ));
1752 };
1753
1754 let result: Value = serde_json::from_str(&text.text).unwrap_or_else(|error| {
1755 tracing::trace!(?error, "Tool result is not valid JSON; sending as string");
1756 json!(text.text)
1757 });
1758
1759 Ok(Self::FunctionResult(FunctionResultContent {
1760 name: Some(id),
1761 is_error: None,
1762 result: Some(result),
1763 call_id: Some(call_id),
1764 }))
1765 }
1766 message::UserContent::Image(message::Image {
1767 data, media_type, ..
1768 }) => {
1769 let media_type = media_type.ok_or_else(|| {
1770 message::MessageError::ConversionError(
1771 "Media type for image is required for Gemini".to_string(),
1772 )
1773 })?;
1774 let mime_type = media_type.to_mime_type().to_string();
1775 let (data, uri) = split_data_uri(data)?;
1776 Ok(Self::Image(ImageContent {
1777 data,
1778 uri,
1779 mime_type: Some(mime_type),
1780 resolution: None,
1781 }))
1782 }
1783 message::UserContent::Audio(message::Audio {
1784 data, media_type, ..
1785 }) => {
1786 let media_type = media_type.ok_or_else(|| {
1787 message::MessageError::ConversionError(
1788 "Media type for audio is required for Gemini".to_string(),
1789 )
1790 })?;
1791 let mime_type = media_type.to_mime_type().to_string();
1792 let (data, uri) = split_data_uri(data)?;
1793 Ok(Self::Audio(AudioContent {
1794 data,
1795 uri,
1796 mime_type: Some(mime_type),
1797 }))
1798 }
1799 message::UserContent::Video(message::Video {
1800 data, media_type, ..
1801 }) => {
1802 let media_type = media_type.ok_or_else(|| {
1803 message::MessageError::ConversionError(
1804 "Media type for video is required for Gemini".to_string(),
1805 )
1806 })?;
1807 let mime_type = media_type.to_mime_type().to_string();
1808 let (data, uri) = split_data_uri(data)?;
1809 Ok(Self::Video(VideoContent {
1810 data,
1811 uri,
1812 mime_type: Some(mime_type),
1813 resolution: None,
1814 }))
1815 }
1816 message::UserContent::Document(message::Document {
1817 data, media_type, ..
1818 }) => {
1819 let media_type = media_type.ok_or_else(|| {
1820 message::MessageError::ConversionError(
1821 "Media type for document is required for Gemini".to_string(),
1822 )
1823 })?;
1824 let mime_type = media_type.to_mime_type().to_string();
1825 let (data, uri) = split_data_uri(data)?;
1826 Ok(Self::Document(DocumentContent {
1827 data,
1828 uri,
1829 mime_type: Some(mime_type),
1830 }))
1831 }
1832 }
1833 }
1834 }
1835
1836 impl TryFrom<message::AssistantContent> for Content {
1837 type Error = message::MessageError;
1838
1839 fn try_from(content: message::AssistantContent) -> Result<Self, Self::Error> {
1840 match content {
1841 message::AssistantContent::Text(message::Text { text }) => {
1842 Ok(Self::Text(TextContent {
1843 text,
1844 annotations: None,
1845 }))
1846 }
1847 message::AssistantContent::ToolCall(tool_call) => {
1848 let call_id = tool_call.call_id.unwrap_or_else(|| tool_call.id.clone());
1849 Ok(Self::FunctionCall(FunctionCallContent {
1850 name: Some(tool_call.function.name),
1851 arguments: Some(tool_call.function.arguments),
1852 id: Some(call_id),
1853 }))
1854 }
1855 message::AssistantContent::Reasoning(message::Reasoning { content, .. }) => {
1856 let mut signature = None;
1857 let summary = content
1858 .into_iter()
1859 .map(|reasoning_content| {
1860 let text = match reasoning_content {
1861 message::ReasoningContent::Text {
1862 text,
1863 signature: content_signature,
1864 } => {
1865 if signature.is_none() {
1866 signature = content_signature;
1867 }
1868 text
1869 }
1870 message::ReasoningContent::Summary(text)
1871 | message::ReasoningContent::Encrypted(text) => text,
1872 message::ReasoningContent::Redacted { data } => data,
1873 };
1874
1875 ThoughtSummaryContent::Text(TextContent {
1876 text,
1877 annotations: None,
1878 })
1879 })
1880 .collect();
1881
1882 Ok(Self::Thought(ThoughtContent {
1883 signature,
1884 summary: Some(summary),
1885 }))
1886 }
1887 message::AssistantContent::Image(message::Image {
1888 data, media_type, ..
1889 }) => {
1890 let media_type = media_type.ok_or_else(|| {
1891 message::MessageError::ConversionError(
1892 "Media type for image is required for Gemini".to_string(),
1893 )
1894 })?;
1895 let mime_type = media_type.to_mime_type().to_string();
1896 let (data, uri) = split_data_uri(data)?;
1897 Ok(Self::Image(ImageContent {
1898 data,
1899 uri,
1900 mime_type: Some(mime_type),
1901 resolution: None,
1902 }))
1903 }
1904 }
1905 }
1906 }
1907
1908 #[derive(Clone, Debug, Deserialize, Serialize)]
1914 #[serde(rename_all = "snake_case")]
1915 pub enum ResponseModality {
1916 Text,
1917 Image,
1918 Audio,
1919 }
1920
1921 #[derive(Clone, Debug, Deserialize, Serialize)]
1923 #[serde(rename_all = "snake_case")]
1924 pub enum ThinkingLevel {
1925 Minimal,
1926 Low,
1927 Medium,
1928 High,
1929 }
1930
1931 #[derive(Clone, Debug, Deserialize, Serialize)]
1933 #[serde(rename_all = "snake_case")]
1934 pub enum ThinkingSummaries {
1935 Auto,
1936 None,
1937 }
1938
1939 #[derive(Clone, Debug, Deserialize, Serialize)]
1941 #[serde(rename_all = "snake_case")]
1942 pub struct SpeechConfig {
1943 #[serde(skip_serializing_if = "Option::is_none")]
1944 pub voice: Option<String>,
1945 #[serde(skip_serializing_if = "Option::is_none")]
1946 pub language: Option<String>,
1947 #[serde(skip_serializing_if = "Option::is_none")]
1948 pub speaker: Option<String>,
1949 }
1950
1951 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
1953 #[serde(rename_all = "snake_case")]
1954 pub struct GenerationConfig {
1955 #[serde(skip_serializing_if = "Option::is_none")]
1956 pub temperature: Option<f64>,
1957 #[serde(skip_serializing_if = "Option::is_none")]
1958 pub top_p: Option<f64>,
1959 #[serde(skip_serializing_if = "Option::is_none")]
1960 pub seed: Option<u64>,
1961 #[serde(skip_serializing_if = "Option::is_none")]
1962 pub stop_sequences: Option<Vec<String>>,
1963 #[serde(skip_serializing_if = "Option::is_none")]
1964 pub tool_choice: Option<ToolChoice>,
1965 #[serde(skip_serializing_if = "Option::is_none")]
1966 pub thinking_level: Option<ThinkingLevel>,
1967 #[serde(skip_serializing_if = "Option::is_none")]
1968 pub thinking_summaries: Option<ThinkingSummaries>,
1969 #[serde(skip_serializing_if = "Option::is_none")]
1970 pub max_output_tokens: Option<u64>,
1971 #[serde(skip_serializing_if = "Option::is_none")]
1972 pub speech_config: Option<Vec<SpeechConfig>>,
1973 }
1974
1975 impl GenerationConfig {
1976 pub fn is_empty(&self) -> bool {
1978 self.temperature.is_none()
1979 && self.top_p.is_none()
1980 && self.seed.is_none()
1981 && self.stop_sequences.is_none()
1982 && self.tool_choice.is_none()
1983 && self.thinking_level.is_none()
1984 && self.thinking_summaries.is_none()
1985 && self.max_output_tokens.is_none()
1986 && self.speech_config.is_none()
1987 }
1988 }
1989
1990 #[derive(Clone, Debug, Deserialize, Serialize)]
1992 #[serde(untagged)]
1993 pub enum ToolChoice {
1994 Type(ToolChoiceType),
1995 Config(ToolChoiceConfig),
1996 }
1997
1998 #[derive(Clone, Debug, Deserialize, Serialize)]
2000 #[serde(rename_all = "snake_case")]
2001 pub enum ToolChoiceType {
2002 Auto,
2003 Any,
2004 None,
2005 Validated,
2006 }
2007
2008 #[derive(Clone, Debug, Deserialize, Serialize)]
2010 pub struct ToolChoiceConfig {
2011 pub allowed_tools: AllowedTools,
2012 }
2013
2014 #[derive(Clone, Debug, Deserialize, Serialize)]
2016 pub struct AllowedTools {
2017 #[serde(skip_serializing_if = "Option::is_none")]
2018 pub mode: Option<ToolChoiceType>,
2019 #[serde(skip_serializing_if = "Option::is_none")]
2020 pub tools: Option<Vec<String>>,
2021 }
2022
2023 #[derive(Clone, Debug, Deserialize, Serialize)]
2025 #[serde(tag = "type", rename_all = "snake_case")]
2026 pub enum Tool {
2027 Function(FunctionTool),
2028 GoogleSearch,
2029 CodeExecution,
2030 UrlContext,
2031 ComputerUse(ComputerUseTool),
2032 McpServer(McpServerTool),
2033 FileSearch(FileSearchTool),
2034 }
2035
2036 #[derive(Clone, Debug, Deserialize, Serialize)]
2038 pub struct FunctionTool {
2039 #[serde(skip_serializing_if = "Option::is_none")]
2040 pub name: Option<String>,
2041 #[serde(skip_serializing_if = "Option::is_none")]
2042 pub description: Option<String>,
2043 #[serde(skip_serializing_if = "Option::is_none")]
2044 pub parameters: Option<Value>,
2045 }
2046
2047 #[derive(Clone, Debug, Deserialize, Serialize)]
2049 pub struct ComputerUseTool {
2050 #[serde(skip_serializing_if = "Option::is_none")]
2051 pub environment: Option<String>,
2052 #[serde(skip_serializing_if = "Option::is_none")]
2053 pub excluded_predefined_functions: Option<Vec<String>>,
2054 }
2055
2056 #[derive(Clone, Debug, Deserialize, Serialize)]
2058 pub struct McpServerTool {
2059 #[serde(skip_serializing_if = "Option::is_none")]
2060 pub name: Option<String>,
2061 #[serde(skip_serializing_if = "Option::is_none")]
2062 pub url: Option<String>,
2063 #[serde(skip_serializing_if = "Option::is_none")]
2064 pub headers: Option<Value>,
2065 #[serde(skip_serializing_if = "Option::is_none")]
2066 pub allowed_tools: Option<AllowedTools>,
2067 }
2068
2069 #[derive(Clone, Debug, Deserialize, Serialize)]
2071 pub struct FileSearchTool {
2072 #[serde(skip_serializing_if = "Option::is_none")]
2073 pub file_search_store_names: Option<Vec<String>>,
2074 #[serde(skip_serializing_if = "Option::is_none")]
2075 pub top_k: Option<u64>,
2076 #[serde(skip_serializing_if = "Option::is_none")]
2077 pub metadata_filter: Option<String>,
2078 }
2079
2080 impl TryFrom<crate::completion::ToolDefinition> for Tool {
2081 type Error = CompletionError;
2082
2083 fn try_from(tool: crate::completion::ToolDefinition) -> Result<Self, Self::Error> {
2084 Ok(Tool::Function(FunctionTool {
2085 name: Some(tool.name),
2086 description: Some(tool.description),
2087 parameters: Some(tool.parameters),
2088 }))
2089 }
2090 }
2091
2092 impl TryFrom<message::ToolChoice> for ToolChoice {
2093 type Error = CompletionError;
2094
2095 fn try_from(tool_choice: message::ToolChoice) -> Result<Self, Self::Error> {
2096 match tool_choice {
2097 message::ToolChoice::Auto => Ok(ToolChoice::Type(ToolChoiceType::Auto)),
2098 message::ToolChoice::None => Ok(ToolChoice::Type(ToolChoiceType::None)),
2099 message::ToolChoice::Required => Ok(ToolChoice::Type(ToolChoiceType::Any)),
2100 message::ToolChoice::Specific { function_names } => {
2101 Ok(ToolChoice::Config(ToolChoiceConfig {
2102 allowed_tools: AllowedTools {
2103 mode: Some(ToolChoiceType::Validated),
2104 tools: Some(function_names),
2105 },
2106 }))
2107 }
2108 }
2109 }
2110 }
2111
2112 #[derive(Clone, Debug, Deserialize, Serialize)]
2114 #[serde(tag = "type", rename_all = "kebab-case")]
2115 pub enum AgentConfig {
2116 Dynamic,
2117 DeepResearch {
2118 #[serde(skip_serializing_if = "Option::is_none")]
2119 thinking_summaries: Option<ThinkingSummaries>,
2120 },
2121 }
2122
2123 #[derive(Clone, Debug, Deserialize, Serialize)]
2125 #[serde(rename_all = "snake_case")]
2126 pub enum MediaResolution {
2127 Low,
2128 Medium,
2129 High,
2130 UltraHigh,
2131 }
2132
2133 #[derive(Clone, Debug, Deserialize, Serialize)]
2139 #[serde(tag = "event_type")]
2140 pub enum InteractionSseEvent {
2141 #[serde(rename = "interaction.start")]
2142 InteractionStart {
2143 interaction: Interaction,
2144 #[serde(skip_serializing_if = "Option::is_none")]
2145 event_id: Option<String>,
2146 },
2147 #[serde(rename = "interaction.complete")]
2148 InteractionComplete {
2149 interaction: Interaction,
2150 #[serde(skip_serializing_if = "Option::is_none")]
2151 event_id: Option<String>,
2152 },
2153 #[serde(rename = "interaction.status_update")]
2154 InteractionStatusUpdate {
2155 interaction_id: String,
2156 status: InteractionStatus,
2157 #[serde(skip_serializing_if = "Option::is_none")]
2158 event_id: Option<String>,
2159 },
2160 #[serde(rename = "content.start")]
2161 ContentStart {
2162 index: i32,
2163 content: Content,
2164 #[serde(skip_serializing_if = "Option::is_none")]
2165 event_id: Option<String>,
2166 },
2167 #[serde(rename = "content.delta")]
2168 ContentDelta {
2169 index: i32,
2170 delta: ContentDelta,
2171 #[serde(skip_serializing_if = "Option::is_none")]
2172 event_id: Option<String>,
2173 },
2174 #[serde(rename = "content.stop")]
2175 ContentStop {
2176 index: i32,
2177 #[serde(skip_serializing_if = "Option::is_none")]
2178 event_id: Option<String>,
2179 },
2180 #[serde(rename = "error")]
2181 Error {
2182 error: ErrorEvent,
2183 #[serde(skip_serializing_if = "Option::is_none")]
2184 event_id: Option<String>,
2185 },
2186 }
2187
2188 #[derive(Clone, Debug, Deserialize, Serialize)]
2190 pub struct ErrorEvent {
2191 pub code: String,
2192 pub message: String,
2193 }
2194
2195 #[derive(Clone, Debug, Deserialize, Serialize)]
2197 #[serde(tag = "type", rename_all = "snake_case")]
2198 pub enum ContentDelta {
2199 Text(TextDelta),
2200 Image(ImageDelta),
2201 Audio(AudioDelta),
2202 Document(DocumentDelta),
2203 Video(VideoDelta),
2204 ThoughtSummary(ThoughtSummaryDelta),
2205 ThoughtSignature(ThoughtSignatureDelta),
2206 FunctionCall(FunctionCallDelta),
2207 FunctionResult(FunctionResultDelta),
2208 CodeExecutionCall(CodeExecutionCallDelta),
2209 CodeExecutionResult(CodeExecutionResultDelta),
2210 UrlContextCall(UrlContextCallDelta),
2211 UrlContextResult(UrlContextResultDelta),
2212 GoogleSearchCall(GoogleSearchCallDelta),
2213 GoogleSearchResult(GoogleSearchResultDelta),
2214 McpServerToolCall(McpServerToolCallDelta),
2215 McpServerToolResult(McpServerToolResultDelta),
2216 FileSearchResult(FileSearchResultDelta),
2217 }
2218
2219 #[derive(Clone, Debug, Deserialize, Serialize)]
2221 pub struct TextDelta {
2222 #[serde(skip_serializing_if = "Option::is_none")]
2223 pub text: Option<String>,
2224 #[serde(skip_serializing_if = "Option::is_none")]
2225 pub annotations: Option<Vec<Annotation>>,
2226 }
2227
2228 #[derive(Clone, Debug, Deserialize, Serialize)]
2230 pub struct ImageDelta {
2231 #[serde(skip_serializing_if = "Option::is_none")]
2232 pub data: Option<String>,
2233 #[serde(skip_serializing_if = "Option::is_none")]
2234 pub uri: Option<String>,
2235 #[serde(skip_serializing_if = "Option::is_none")]
2236 pub mime_type: Option<String>,
2237 #[serde(skip_serializing_if = "Option::is_none")]
2238 pub resolution: Option<MediaResolution>,
2239 }
2240
2241 #[derive(Clone, Debug, Deserialize, Serialize)]
2243 pub struct AudioDelta {
2244 #[serde(skip_serializing_if = "Option::is_none")]
2245 pub data: Option<String>,
2246 #[serde(skip_serializing_if = "Option::is_none")]
2247 pub uri: Option<String>,
2248 #[serde(skip_serializing_if = "Option::is_none")]
2249 pub mime_type: Option<String>,
2250 }
2251
2252 #[derive(Clone, Debug, Deserialize, Serialize)]
2254 pub struct DocumentDelta {
2255 #[serde(skip_serializing_if = "Option::is_none")]
2256 pub data: Option<String>,
2257 #[serde(skip_serializing_if = "Option::is_none")]
2258 pub uri: Option<String>,
2259 #[serde(skip_serializing_if = "Option::is_none")]
2260 pub mime_type: Option<String>,
2261 }
2262
2263 #[derive(Clone, Debug, Deserialize, Serialize)]
2265 pub struct VideoDelta {
2266 #[serde(skip_serializing_if = "Option::is_none")]
2267 pub data: Option<String>,
2268 #[serde(skip_serializing_if = "Option::is_none")]
2269 pub uri: Option<String>,
2270 #[serde(skip_serializing_if = "Option::is_none")]
2271 pub mime_type: Option<String>,
2272 #[serde(skip_serializing_if = "Option::is_none")]
2273 pub resolution: Option<MediaResolution>,
2274 }
2275
2276 #[derive(Clone, Debug, Deserialize, Serialize)]
2278 pub struct ThoughtSummaryDelta {
2279 pub content: ThoughtSummaryContent,
2280 }
2281
2282 #[derive(Clone, Debug, Deserialize, Serialize)]
2284 pub struct ThoughtSignatureDelta {
2285 pub signature: String,
2286 }
2287
2288 #[derive(Clone, Debug, Deserialize, Serialize)]
2290 pub struct FunctionCallDelta {
2291 #[serde(skip_serializing_if = "Option::is_none")]
2292 pub name: Option<String>,
2293 #[serde(skip_serializing_if = "Option::is_none")]
2294 pub arguments: Option<Value>,
2295 #[serde(skip_serializing_if = "Option::is_none")]
2296 pub id: Option<String>,
2297 }
2298
2299 #[derive(Clone, Debug, Deserialize, Serialize)]
2301 pub struct FunctionResultDelta {
2302 #[serde(skip_serializing_if = "Option::is_none")]
2303 pub name: Option<String>,
2304 #[serde(skip_serializing_if = "Option::is_none")]
2305 pub result: Option<Value>,
2306 #[serde(skip_serializing_if = "Option::is_none")]
2307 pub call_id: Option<String>,
2308 #[serde(skip_serializing_if = "Option::is_none")]
2309 pub is_error: Option<bool>,
2310 }
2311
2312 #[derive(Clone, Debug, Deserialize, Serialize)]
2314 pub struct CodeExecutionCallDelta {
2315 #[serde(skip_serializing_if = "Option::is_none")]
2316 pub arguments: Option<CodeExecutionCallArguments>,
2317 #[serde(skip_serializing_if = "Option::is_none")]
2318 pub id: Option<String>,
2319 }
2320
2321 #[derive(Clone, Debug, Deserialize, Serialize)]
2323 pub struct CodeExecutionResultDelta {
2324 #[serde(skip_serializing_if = "Option::is_none")]
2325 pub result: Option<String>,
2326 #[serde(skip_serializing_if = "Option::is_none")]
2327 pub is_error: Option<bool>,
2328 #[serde(skip_serializing_if = "Option::is_none")]
2329 pub signature: Option<String>,
2330 #[serde(skip_serializing_if = "Option::is_none")]
2331 pub call_id: Option<String>,
2332 }
2333
2334 #[derive(Clone, Debug, Deserialize, Serialize)]
2336 pub struct UrlContextCallDelta {
2337 #[serde(skip_serializing_if = "Option::is_none")]
2338 pub arguments: Option<UrlContextCallArguments>,
2339 #[serde(skip_serializing_if = "Option::is_none")]
2340 pub id: Option<String>,
2341 }
2342
2343 #[derive(Clone, Debug, Deserialize, Serialize)]
2345 pub struct UrlContextResultDelta {
2346 #[serde(skip_serializing_if = "Option::is_none")]
2347 pub result: Option<Vec<UrlContextResult>>,
2348 #[serde(skip_serializing_if = "Option::is_none")]
2349 pub signature: Option<String>,
2350 #[serde(skip_serializing_if = "Option::is_none")]
2351 pub is_error: Option<bool>,
2352 #[serde(skip_serializing_if = "Option::is_none")]
2353 pub call_id: Option<String>,
2354 }
2355
2356 #[derive(Clone, Debug, Deserialize, Serialize)]
2358 pub struct GoogleSearchCallDelta {
2359 #[serde(skip_serializing_if = "Option::is_none")]
2360 pub arguments: Option<GoogleSearchCallArguments>,
2361 #[serde(skip_serializing_if = "Option::is_none")]
2362 pub id: Option<String>,
2363 }
2364
2365 #[derive(Clone, Debug, Deserialize, Serialize)]
2367 pub struct GoogleSearchResultDelta {
2368 #[serde(skip_serializing_if = "Option::is_none")]
2369 pub result: Option<Vec<GoogleSearchResult>>,
2370 #[serde(skip_serializing_if = "Option::is_none")]
2371 pub signature: Option<String>,
2372 #[serde(skip_serializing_if = "Option::is_none")]
2373 pub is_error: Option<bool>,
2374 #[serde(skip_serializing_if = "Option::is_none")]
2375 pub call_id: Option<String>,
2376 }
2377
2378 #[derive(Clone, Debug, Deserialize, Serialize)]
2380 pub struct McpServerToolCallDelta {
2381 #[serde(skip_serializing_if = "Option::is_none")]
2382 pub name: Option<String>,
2383 #[serde(skip_serializing_if = "Option::is_none")]
2384 pub server_name: Option<String>,
2385 #[serde(skip_serializing_if = "Option::is_none")]
2386 pub arguments: Option<Value>,
2387 #[serde(skip_serializing_if = "Option::is_none")]
2388 pub id: Option<String>,
2389 }
2390
2391 #[derive(Clone, Debug, Deserialize, Serialize)]
2393 pub struct McpServerToolResultDelta {
2394 #[serde(skip_serializing_if = "Option::is_none")]
2395 pub name: Option<String>,
2396 #[serde(skip_serializing_if = "Option::is_none")]
2397 pub server_name: Option<String>,
2398 #[serde(skip_serializing_if = "Option::is_none")]
2399 pub result: Option<Value>,
2400 #[serde(skip_serializing_if = "Option::is_none")]
2401 pub call_id: Option<String>,
2402 }
2403
2404 #[derive(Clone, Debug, Deserialize, Serialize)]
2406 pub struct FileSearchResultDelta {
2407 #[serde(skip_serializing_if = "Option::is_none")]
2408 pub result: Option<Vec<FileSearchResult>>,
2409 }
2410}
2411
2412#[cfg(test)]
2413mod tests {
2414 use super::*;
2415 use crate::OneOrMany;
2416 use crate::completion::{CompletionRequest, Message};
2417 use crate::message::{self, ToolChoice as MessageToolChoice};
2418 use serde_json::json;
2419
2420 #[test]
2421 fn test_create_request_body_simple() {
2422 let prompt = Message::User {
2423 content: OneOrMany::one(message::UserContent::text("Hello")),
2424 };
2425
2426 let request = CompletionRequest {
2427 model: None,
2428 preamble: Some("Be precise.".to_string()),
2429 chat_history: OneOrMany::one(prompt),
2430 documents: vec![],
2431 tools: vec![],
2432 temperature: Some(0.7),
2433 max_tokens: Some(128),
2434 tool_choice: Some(MessageToolChoice::Required),
2435 additional_params: None,
2436 output_schema: None,
2437 };
2438
2439 let result = create_request_body("gemini-2.5-flash".to_string(), request, Some(false))
2440 .expect("request should build");
2441
2442 assert_eq!(result.model.as_deref(), Some("gemini-2.5-flash"));
2443 assert!(result.agent.is_none());
2444 assert_eq!(result.stream, Some(false));
2445 assert_eq!(result.system_instruction.as_deref(), Some("Be precise."));
2446
2447 let config = result.generation_config.expect("generation config missing");
2448 assert_eq!(config.temperature, Some(0.7));
2449 assert_eq!(config.max_output_tokens, Some(128));
2450 assert!(matches!(
2451 config.tool_choice,
2452 Some(ToolChoice::Type(ToolChoiceType::Any))
2453 ));
2454
2455 let InteractionInput::Turns(turns) = result.input else {
2456 panic!("expected turns input");
2457 };
2458 assert_eq!(turns.len(), 1);
2459 let turn = &turns[0];
2460 assert!(matches!(turn.role, Role::User));
2461 let TurnContent::Contents(contents) = &turn.content else {
2462 panic!("expected content array");
2463 };
2464 assert_eq!(contents.len(), 1);
2465 match &contents[0] {
2466 Content::Text(TextContent { text, .. }) => assert_eq!(text, "Hello"),
2467 other => panic!("unexpected content: {other:?}"),
2468 }
2469 }
2470
2471 #[test]
2472 fn test_tool_result_requires_call_id() {
2473 let content = message::UserContent::ToolResult(message::ToolResult {
2474 id: "get_weather".to_string(),
2475 call_id: None,
2476 content: OneOrMany::one(message::ToolResultContent::text("ok")),
2477 });
2478
2479 let err = Content::try_from(content).expect_err("should require call_id");
2480 assert!(format!("{err}").contains("call_id"));
2481 }
2482
2483 #[test]
2484 fn test_response_function_call_mapping() {
2485 let interaction = Interaction {
2486 id: "interaction-1".to_string(),
2487 outputs: vec![Content::FunctionCall(FunctionCallContent {
2488 name: Some("get_weather".to_string()),
2489 arguments: Some(json!({"location": "Paris"})),
2490 id: Some("call-123".to_string()),
2491 })],
2492 usage: Some(InteractionUsage {
2493 total_input_tokens: Some(5),
2494 total_output_tokens: Some(7),
2495 total_tokens: Some(12),
2496 }),
2497 ..Default::default()
2498 };
2499
2500 let response: completion::CompletionResponse<Interaction> =
2501 interaction.try_into().expect("conversion should succeed");
2502
2503 let choice = response.choice.first();
2504 match choice {
2505 completion::AssistantContent::ToolCall(tool_call) => {
2506 assert_eq!(tool_call.function.name, "get_weather");
2507 assert_eq!(tool_call.call_id.as_deref(), Some("call-123"));
2508 }
2509 other => panic!("unexpected content: {other:?}"),
2510 }
2511
2512 assert_eq!(response.usage.input_tokens, 5);
2513 assert_eq!(response.usage.output_tokens, 7);
2514 assert_eq!(response.usage.total_tokens, 12);
2515 }
2516
2517 #[test]
2518 fn test_google_search_tool_serialization() {
2519 let tool = Tool::GoogleSearch;
2520 let value = serde_json::to_value(tool).expect("tool should serialize");
2521 assert_eq!(value, json!({ "type": "google_search" }));
2522 }
2523
2524 #[test]
2525 fn test_url_context_tool_serialization() {
2526 let tool = Tool::UrlContext;
2527 let value = serde_json::to_value(tool).expect("tool should serialize");
2528 assert_eq!(value, json!({ "type": "url_context" }));
2529 }
2530
2531 #[test]
2532 fn test_code_execution_tool_serialization() {
2533 let tool = Tool::CodeExecution;
2534 let value = serde_json::to_value(tool).expect("tool should serialize");
2535 assert_eq!(value, json!({ "type": "code_execution" }));
2536 }
2537
2538 #[test]
2539 fn test_google_search_helpers() {
2540 let interaction = Interaction {
2541 outputs: vec![
2542 Content::GoogleSearchCall(GoogleSearchCallContent {
2543 arguments: Some(GoogleSearchCallArguments {
2544 queries: Some(vec!["query-one".to_string(), "query-two".to_string()]),
2545 }),
2546 id: Some("call-1".to_string()),
2547 }),
2548 Content::GoogleSearchResult(GoogleSearchResultContent {
2549 result: Some(vec![GoogleSearchResult {
2550 url: Some("https://example.com".to_string()),
2551 title: Some("Example One".to_string()),
2552 rendered_content: None,
2553 }]),
2554 signature: None,
2555 is_error: None,
2556 call_id: Some("call-1".to_string()),
2557 }),
2558 Content::GoogleSearchCall(GoogleSearchCallContent {
2559 arguments: Some(GoogleSearchCallArguments {
2560 queries: Some(vec!["query-three".to_string()]),
2561 }),
2562 id: Some("call-2".to_string()),
2563 }),
2564 Content::GoogleSearchResult(GoogleSearchResultContent {
2565 result: Some(vec![GoogleSearchResult {
2566 url: Some("https://example.org".to_string()),
2567 title: Some("Example Two".to_string()),
2568 rendered_content: None,
2569 }]),
2570 signature: None,
2571 is_error: None,
2572 call_id: Some("call-2".to_string()),
2573 }),
2574 ],
2575 ..Default::default()
2576 };
2577
2578 let exchanges = interaction.google_search_exchanges();
2579 assert_eq!(exchanges.len(), 2);
2580 assert_eq!(exchanges[0].call_id.as_deref(), Some("call-1"));
2581 assert_eq!(
2582 exchanges[0].queries(),
2583 vec!["query-one".to_string(), "query-two".to_string()]
2584 );
2585 let exchange_results = exchanges[0].result_items();
2586 assert_eq!(exchange_results.len(), 1);
2587 assert_eq!(exchange_results[0].title.as_deref(), Some("Example One"));
2588
2589 assert_eq!(exchanges[1].call_id.as_deref(), Some("call-2"));
2590 assert_eq!(exchanges[1].queries(), vec!["query-three".to_string()]);
2591 let exchange_results = exchanges[1].result_items();
2592 assert_eq!(exchange_results.len(), 1);
2593 assert_eq!(exchange_results[0].title.as_deref(), Some("Example Two"));
2594
2595 let queries = interaction.google_search_queries();
2596 assert_eq!(queries, vec!["query-one", "query-two", "query-three"]);
2597
2598 let results = interaction.google_search_results();
2599 assert_eq!(results.len(), 2);
2600 assert_eq!(results[0].title.as_deref(), Some("Example One"));
2601 assert_eq!(results[1].title.as_deref(), Some("Example Two"));
2602
2603 let call_contents = interaction.google_search_call_contents();
2604 assert_eq!(call_contents.len(), 2);
2605 assert_eq!(call_contents[0].id.as_deref(), Some("call-1"));
2606 assert_eq!(call_contents[1].id.as_deref(), Some("call-2"));
2607
2608 let result_contents = interaction.google_search_result_contents();
2609 assert_eq!(result_contents.len(), 2);
2610 assert_eq!(result_contents[0].call_id.as_deref(), Some("call-1"));
2611 assert_eq!(result_contents[1].call_id.as_deref(), Some("call-2"));
2612 }
2613
2614 #[test]
2615 fn test_google_search_helpers_without_call_id() {
2616 let interaction = Interaction {
2617 outputs: vec![
2618 Content::GoogleSearchCall(GoogleSearchCallContent {
2619 arguments: Some(GoogleSearchCallArguments {
2620 queries: Some(vec!["query-one".to_string()]),
2621 }),
2622 id: None,
2623 }),
2624 Content::GoogleSearchResult(GoogleSearchResultContent {
2625 result: Some(vec![GoogleSearchResult {
2626 url: Some("https://example.com".to_string()),
2627 title: Some("Example One".to_string()),
2628 rendered_content: None,
2629 }]),
2630 signature: None,
2631 is_error: None,
2632 call_id: None,
2633 }),
2634 Content::GoogleSearchCall(GoogleSearchCallContent {
2635 arguments: Some(GoogleSearchCallArguments {
2636 queries: Some(vec!["query-two".to_string()]),
2637 }),
2638 id: Some("call-2".to_string()),
2639 }),
2640 Content::GoogleSearchResult(GoogleSearchResultContent {
2641 result: Some(vec![GoogleSearchResult {
2642 url: Some("https://example.org".to_string()),
2643 title: Some("Example Two".to_string()),
2644 rendered_content: None,
2645 }]),
2646 signature: None,
2647 is_error: None,
2648 call_id: None,
2649 }),
2650 ],
2651 ..Default::default()
2652 };
2653
2654 let exchanges = interaction.google_search_exchanges();
2655 assert_eq!(exchanges.len(), 2);
2656
2657 let no_id = exchanges
2658 .iter()
2659 .find(|exchange| exchange.call_id.is_none())
2660 .expect("expected no-id exchange");
2661 assert_eq!(no_id.calls.len(), 1);
2662 assert_eq!(no_id.results.len(), 1);
2663
2664 let with_id = exchanges
2665 .iter()
2666 .find(|exchange| exchange.call_id.as_deref() == Some("call-2"))
2667 .expect("expected call-2 exchange");
2668 assert_eq!(with_id.calls.len(), 1);
2669 assert_eq!(with_id.results.len(), 1);
2670 }
2671
2672 #[test]
2673 fn test_url_context_helpers() {
2674 let interaction = Interaction {
2675 outputs: vec![
2676 Content::UrlContextCall(UrlContextCallContent {
2677 arguments: Some(UrlContextCallArguments {
2678 urls: Some(vec![
2679 "https://example.com".to_string(),
2680 "https://example.org".to_string(),
2681 ]),
2682 }),
2683 id: Some("call-1".to_string()),
2684 }),
2685 Content::UrlContextResult(UrlContextResultContent {
2686 result: Some(vec![UrlContextResult {
2687 url: Some("https://example.com".to_string()),
2688 status: Some("success".to_string()),
2689 }]),
2690 signature: None,
2691 is_error: None,
2692 call_id: Some("call-1".to_string()),
2693 }),
2694 ],
2695 ..Default::default()
2696 };
2697
2698 let exchanges = interaction.url_context_exchanges();
2699 assert_eq!(exchanges.len(), 1);
2700 assert_eq!(exchanges[0].call_id.as_deref(), Some("call-1"));
2701 assert_eq!(
2702 exchanges[0].urls(),
2703 vec!["https://example.com", "https://example.org"]
2704 );
2705 let results = exchanges[0].result_items();
2706 assert_eq!(results.len(), 1);
2707 assert_eq!(results[0].status.as_deref(), Some("success"));
2708
2709 let urls = interaction.url_context_urls();
2710 assert_eq!(urls, vec!["https://example.com", "https://example.org"]);
2711
2712 let results = interaction.url_context_results();
2713 assert_eq!(results.len(), 1);
2714 assert_eq!(results[0].url.as_deref(), Some("https://example.com"));
2715
2716 let call_contents = interaction.url_context_call_contents();
2717 assert_eq!(call_contents.len(), 1);
2718 assert_eq!(call_contents[0].id.as_deref(), Some("call-1"));
2719
2720 let result_contents = interaction.url_context_result_contents();
2721 assert_eq!(result_contents.len(), 1);
2722 assert_eq!(result_contents[0].call_id.as_deref(), Some("call-1"));
2723 }
2724
2725 #[test]
2726 fn test_url_context_helpers_without_call_id() {
2727 let interaction = Interaction {
2728 outputs: vec![
2729 Content::UrlContextCall(UrlContextCallContent {
2730 arguments: Some(UrlContextCallArguments {
2731 urls: Some(vec!["https://example.com".to_string()]),
2732 }),
2733 id: None,
2734 }),
2735 Content::UrlContextResult(UrlContextResultContent {
2736 result: Some(vec![UrlContextResult {
2737 url: Some("https://example.com".to_string()),
2738 status: Some("success".to_string()),
2739 }]),
2740 signature: None,
2741 is_error: None,
2742 call_id: None,
2743 }),
2744 Content::UrlContextCall(UrlContextCallContent {
2745 arguments: Some(UrlContextCallArguments {
2746 urls: Some(vec!["https://example.org".to_string()]),
2747 }),
2748 id: Some("call-2".to_string()),
2749 }),
2750 Content::UrlContextResult(UrlContextResultContent {
2751 result: Some(vec![UrlContextResult {
2752 url: Some("https://example.org".to_string()),
2753 status: Some("success".to_string()),
2754 }]),
2755 signature: None,
2756 is_error: None,
2757 call_id: None,
2758 }),
2759 ],
2760 ..Default::default()
2761 };
2762
2763 let exchanges = interaction.url_context_exchanges();
2764 assert_eq!(exchanges.len(), 2);
2765
2766 let no_id = exchanges
2767 .iter()
2768 .find(|exchange| exchange.call_id.is_none())
2769 .expect("expected no-id exchange");
2770 assert_eq!(no_id.calls.len(), 1);
2771 assert_eq!(no_id.results.len(), 1);
2772
2773 let with_id = exchanges
2774 .iter()
2775 .find(|exchange| exchange.call_id.as_deref() == Some("call-2"))
2776 .expect("expected call-2 exchange");
2777 assert_eq!(with_id.calls.len(), 1);
2778 assert_eq!(with_id.results.len(), 1);
2779 }
2780
2781 #[test]
2782 fn test_code_execution_helpers() {
2783 let interaction = Interaction {
2784 outputs: vec![
2785 Content::CodeExecutionCall(CodeExecutionCallContent {
2786 arguments: Some(CodeExecutionCallArguments {
2787 language: Some("python".to_string()),
2788 code: Some("print(2 + 2)".to_string()),
2789 }),
2790 id: Some("call-1".to_string()),
2791 }),
2792 Content::CodeExecutionResult(CodeExecutionResultContent {
2793 result: Some("4\n".to_string()),
2794 signature: None,
2795 is_error: None,
2796 call_id: Some("call-1".to_string()),
2797 }),
2798 ],
2799 ..Default::default()
2800 };
2801
2802 let exchanges = interaction.code_execution_exchanges();
2803 assert_eq!(exchanges.len(), 1);
2804 assert_eq!(exchanges[0].call_id.as_deref(), Some("call-1"));
2805 assert_eq!(exchanges[0].code_snippets(), vec!["print(2 + 2)"]);
2806 assert_eq!(exchanges[0].outputs(), vec!["4\n"]);
2807
2808 let calls = interaction.code_execution_call_contents();
2809 assert_eq!(calls.len(), 1);
2810 assert_eq!(calls[0].id.as_deref(), Some("call-1"));
2811
2812 let results = interaction.code_execution_result_contents();
2813 assert_eq!(results.len(), 1);
2814 assert_eq!(results[0].call_id.as_deref(), Some("call-1"));
2815
2816 let snippets = interaction.code_execution_snippets();
2817 assert_eq!(snippets, vec!["print(2 + 2)"]);
2818
2819 let outputs = interaction.code_execution_outputs();
2820 assert_eq!(outputs, vec!["4\n"]);
2821 }
2822
2823 #[test]
2824 fn test_code_execution_helpers_without_call_id() {
2825 let interaction = Interaction {
2826 outputs: vec![
2827 Content::CodeExecutionCall(CodeExecutionCallContent {
2828 arguments: Some(CodeExecutionCallArguments {
2829 language: Some("python".to_string()),
2830 code: Some("print(1 + 1)".to_string()),
2831 }),
2832 id: None,
2833 }),
2834 Content::CodeExecutionResult(CodeExecutionResultContent {
2835 result: Some("2\n".to_string()),
2836 signature: None,
2837 is_error: None,
2838 call_id: None,
2839 }),
2840 Content::CodeExecutionCall(CodeExecutionCallContent {
2841 arguments: Some(CodeExecutionCallArguments {
2842 language: Some("python".to_string()),
2843 code: Some("print(2 + 2)".to_string()),
2844 }),
2845 id: Some("call-2".to_string()),
2846 }),
2847 Content::CodeExecutionResult(CodeExecutionResultContent {
2848 result: Some("4\n".to_string()),
2849 signature: None,
2850 is_error: None,
2851 call_id: None,
2852 }),
2853 ],
2854 ..Default::default()
2855 };
2856
2857 let exchanges = interaction.code_execution_exchanges();
2858 assert_eq!(exchanges.len(), 2);
2859
2860 let no_id = exchanges
2861 .iter()
2862 .find(|exchange| exchange.call_id.is_none())
2863 .expect("expected no-id exchange");
2864 assert_eq!(no_id.calls.len(), 1);
2865 assert_eq!(no_id.results.len(), 1);
2866
2867 let with_id = exchanges
2868 .iter()
2869 .find(|exchange| exchange.call_id.as_deref() == Some("call-2"))
2870 .expect("expected call-2 exchange");
2871 assert_eq!(with_id.calls.len(), 1);
2872 assert_eq!(with_id.results.len(), 1);
2873 }
2874
2875 #[test]
2876 fn test_interaction_status_helpers() {
2877 let mut interaction = Interaction {
2878 status: Some(InteractionStatus::InProgress),
2879 ..Default::default()
2880 };
2881 assert!(!interaction.is_terminal());
2882 assert!(!interaction.is_completed());
2883
2884 interaction.status = Some(InteractionStatus::Completed);
2885 assert!(interaction.is_terminal());
2886 assert!(interaction.is_completed());
2887
2888 interaction.status = Some(InteractionStatus::Failed);
2889 assert!(interaction.is_terminal());
2890 assert!(!interaction.is_completed());
2891 }
2892
2893 #[test]
2894 fn test_build_interaction_stream_path() {
2895 let path = build_interaction_stream_path("interaction-123", None);
2896 assert_eq!(path, "/v1beta/interactions/interaction-123?stream=true");
2897
2898 let path = build_interaction_stream_path("interaction-123", Some("event-456"));
2899 assert_eq!(
2900 path,
2901 "/v1beta/interactions/interaction-123?stream=true&last_event_id=event-456"
2902 );
2903 }
2904
2905 #[test]
2906 fn test_inline_citations_from_annotations() {
2907 let text_content = TextContent {
2908 text: "Hello world".to_string(),
2909 annotations: Some(vec![
2910 Annotation {
2911 start_index: Some(6),
2912 end_index: Some(11),
2913 source: Some("https://example.com".to_string()),
2914 },
2915 Annotation {
2916 start_index: Some(0),
2917 end_index: Some(5),
2918 source: Some("https://hello.example".to_string()),
2919 },
2920 ]),
2921 };
2922
2923 let cited = text_content.with_inline_citations();
2924 assert_eq!(
2925 cited,
2926 "Hello[1](https://hello.example) world[2](https://example.com)"
2927 );
2928
2929 let interaction = Interaction {
2930 outputs: vec![Content::Text(text_content)],
2931 ..Default::default()
2932 };
2933
2934 let cited_text = interaction.text_with_inline_citations();
2935 assert_eq!(
2936 cited_text.as_deref(),
2937 Some("Hello[1](https://hello.example) world[2](https://example.com)")
2938 );
2939 }
2940}