1use crate::completion::CompletionRequest;
13use crate::error::EncodeError;
14use crate::message::{self, MimeType};
15use crate::telemetry::GenAiOperation;
16use crate::wire::{Descriptor, Mode};
17use base64::{Engine, prelude::BASE64_STANDARD};
18use serde_json::{Map, Value};
19use url::form_urlencoded;
20
21pub mod streaming;
23pub use interactions_api_types::*;
24
25pub(crate) const PROVIDER_NAME: &str = "gcp.gemini";
27
28pub(crate) const ISSUER: crate::message::Issuer =
31 crate::message::Issuer::from_static(PROVIDER_NAME);
32
33#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
36pub struct Interactions {
37 pub provider: crate::providers::gemini::GeminiConfig,
39 pub model: String,
41}
42
43impl Interactions {
44 pub fn new(provider: crate::providers::gemini::GeminiConfig, model: impl Into<String>) -> Self {
46 Self {
47 provider,
48 model: model.into(),
49 }
50 }
51}
52
53impl crate::wire::Wire for Interactions {
54 type Op = crate::operation::Completion;
55 type Payload = crate::wire::Encoded;
56 type Frame = crate::wire::WireFrame;
57 type Decoder<'id> = streaming::InteractionsDecoder<'id>;
58
59 fn describe(&self) -> Descriptor<'_> {
60 Descriptor::new(PROVIDER_NAME)
61 .model(self.model.as_str())
62 .telemetry(|mode| match mode {
63 Mode::Unary => GenAiOperation::Interactions,
64 Mode::Streaming => GenAiOperation::InteractionsStreaming,
65 })
66 }
67
68 fn encode(
69 &self,
70 request: CompletionRequest,
71 mode: crate::wire::Mode,
72 ) -> Result<crate::wire::Encoded, EncodeError> {
73 let request = request.replayable_to(&[ISSUER])?;
74 let streaming = matches!(mode, crate::wire::Mode::Streaming);
77 let body = create_request_body(self.model.clone(), request, Some(streaming))?;
78 crate::providers::internal::trace_json(
79 if streaming {
80 crate::providers::internal::LogTarget::Streaming
81 } else {
82 crate::providers::internal::LogTarget::Completions
83 },
84 "Gemini interactions completion request",
85 &body,
86 );
87 let (path, framing) = if streaming {
88 (
89 "/v1beta/interactions?alt=sse",
90 crate::http_client::framing::Framing::Sse,
91 )
92 } else {
93 (
94 "/v1beta/interactions",
95 crate::http_client::framing::Framing::Whole,
96 )
97 };
98 let request = http::Request::post(self.provider.interactions_uri(path))
99 .header("Content-Type", "application/json")
100 .header(
101 crate::providers::gemini::GeminiConfig::INTERACTIONS_KEY_HEADER,
102 self.provider.api_key.expose(),
103 )
104 .body(crate::wire::Body::Bytes(serde_json::to_vec(&body)?))?;
105 Ok(crate::wire::Encoded::new(request, framing))
107 }
108
109 fn decoder<'id>(&self) -> Self::Decoder<'id> {
110 streaming::InteractionsDecoder::default()
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
118pub struct InteractionResume {
119 pub provider: crate::providers::gemini::GeminiConfig,
121 pub interaction_id: String,
123 pub last_event_id: Option<String>,
126}
127
128impl InteractionResume {
129 pub fn new(
131 provider: crate::providers::gemini::GeminiConfig,
132 interaction_id: impl Into<String>,
133 ) -> Self {
134 Self {
135 provider,
136 interaction_id: interaction_id.into(),
137 last_event_id: None,
138 }
139 }
140
141 pub fn after_event(mut self, last_event_id: impl Into<String>) -> Self {
143 self.last_event_id = Some(last_event_id.into());
144 self
145 }
146}
147
148impl crate::wire::Wire for InteractionResume {
149 type Op = crate::operation::Completion;
150 type Payload = crate::wire::Encoded;
151 type Frame = crate::wire::WireFrame;
152 type Decoder<'id> = streaming::InteractionsDecoder<'id>;
153
154 fn describe(&self) -> Descriptor<'_> {
156 Descriptor::new(PROVIDER_NAME).telemetry(|mode| match mode {
157 Mode::Unary => GenAiOperation::Interactions,
158 Mode::Streaming => GenAiOperation::InteractionsStreaming,
159 })
160 }
161
162 fn encode(
166 &self,
167 _request: CompletionRequest,
168 mode: crate::wire::Mode,
169 ) -> Result<crate::wire::Encoded, EncodeError> {
170 let (path, framing) = match mode {
171 Mode::Unary => (
172 format!("/v1beta/interactions/{}", self.interaction_id),
173 crate::http_client::framing::Framing::Whole,
174 ),
175 Mode::Streaming => (
176 format!(
177 "{}&alt=sse",
178 build_interaction_stream_path(
179 &self.interaction_id,
180 self.last_event_id.as_deref(),
181 )
182 ),
183 crate::http_client::framing::Framing::Sse,
184 ),
185 };
186 let request = http::Request::get(self.provider.interactions_uri(&path))
187 .header(
188 crate::providers::gemini::GeminiConfig::INTERACTIONS_KEY_HEADER,
189 self.provider.api_key.expose(),
190 )
191 .body(crate::wire::Body::empty())?;
192 Ok(crate::wire::Encoded::new(request, framing))
193 }
194
195 fn decoder<'id>(&self) -> Self::Decoder<'id> {
196 streaming::InteractionsDecoder::default()
197 }
198}
199
200pub(crate) fn create_request_body(
201 model: String,
202 completion_request: CompletionRequest,
203 stream_override: Option<bool>,
204) -> Result<CreateInteractionRequest, EncodeError> {
205 let chat_history = completion_request.chat_history_with_documents();
206
207 let mut history = Vec::new();
208 history.extend(chat_history);
209 let (history_system, history) = split_system_messages_from_history(history);
210
211 let tool_ids = crate::providers::internal::wire_ids::WireIds::new(&history);
212 let mut steps = Vec::new();
213 for (position, message) in history.into_iter().enumerate() {
214 let mut converted = Step::from_message(message).map_err(EncodeError::request)?;
215 tool_ids
216 .apply(
217 position,
218 converted.iter_mut().filter_map(|step| match step {
219 Step::FunctionCall(call) => call.id.as_mut(),
220 Step::FunctionResult(result) => result.call_id.as_mut(),
221 _ => None,
222 }),
223 )
224 .map_err(EncodeError::request)?;
225 steps.extend(converted);
226 }
227
228 let input = InteractionInput::Steps(steps);
229
230 let raw_params = completion_request
231 .additional_params
232 .unwrap_or_else(|| Value::Object(Map::new()));
233
234 let mut params: AdditionalParameters = serde_json::from_value(raw_params)?;
235
236 let mut generation_config = params.generation_config.take().unwrap_or_default();
237 if let Some(temp) = completion_request.temperature {
238 generation_config.temperature = Some(temp);
239 }
240 if let Some(max_tokens) = completion_request.max_tokens {
241 generation_config.max_output_tokens = Some(max_tokens);
242 }
243 if let Some(tool_choice) = completion_request.tool_choice {
244 generation_config.tool_choice = Some(tool_choice.try_into()?);
245 }
246 let generation_config = if generation_config.is_empty() {
247 None
248 } else {
249 Some(generation_config)
250 };
251
252 let system_instruction = (!history_system.is_empty())
253 .then(|| history_system.join("\n\n"))
254 .or(params.system_instruction.take());
255
256 let mut tools = Vec::new();
257 if !completion_request.tools.is_empty() {
258 tools.extend(
259 completion_request
260 .tools
261 .into_iter()
262 .map(Tool::try_from)
263 .collect::<Result<Vec<_>, _>>()?,
264 );
265 }
266 if let Some(mut extra_tools) = params.tools.take() {
267 tools.append(&mut extra_tools);
268 }
269 let tools = if tools.is_empty() { None } else { Some(tools) };
270
271 let stream = stream_override.or(params.stream.take());
272
273 let (agent, agent_config) = if params.agent.is_some() {
274 (params.agent.take(), params.agent_config.take())
275 } else {
276 (None, None)
277 };
278
279 let response_format = params.response_format.take();
280 let response_mime_type = params.response_mime_type.take();
281
282 if response_format.is_some() && response_mime_type.is_none() {
283 return Err(EncodeError::request(std::io::Error::new(
284 std::io::ErrorKind::InvalidInput,
285 "response_mime_type is required when response_format is set",
286 )));
287 }
288
289 Ok(CreateInteractionRequest {
290 model: if agent.is_some() { None } else { Some(model) },
291 agent,
292 input,
293 system_instruction,
294 tools,
295 response_format,
296 response_mime_type,
297 stream,
298 store: params.store.take(),
299 background: params.background.take(),
300 generation_config,
301 agent_config,
302 response_modalities: params.response_modalities.take(),
303 previous_interaction_id: params.previous_interaction_id.take(),
304 additional_params: params.additional_params.take(),
305 })
306}
307
308use super::completion::split_system_messages_from_history;
309
310fn build_interaction_stream_path(interaction_id: &str, last_event_id: Option<&str>) -> String {
311 let mut serializer = form_urlencoded::Serializer::new(String::new());
312 serializer.append_pair("stream", "true");
313 if let Some(last_event_id) = last_event_id {
314 serializer.append_pair("last_event_id", last_event_id);
315 }
316 format!(
317 "/v1beta/interactions/{}?{}",
318 interaction_id,
319 serializer.finish()
320 )
321}
322
323fn media_parts<M: MimeType>(
326 data: message::DocumentSourceKind,
327 media_type: Option<M>,
328 kind: &str,
329) -> Result<(Option<String>, Option<String>, String), message::MessageError> {
330 let media_type = media_type.ok_or_else(|| {
331 message::MessageError::ConversionError(format!(
332 "Media type for {kind} is required for Gemini"
333 ))
334 })?;
335 let mime_type = media_type.to_mime_type().to_string();
336 let (data, uri) = split_data_uri(data)?;
337 Ok((data, uri, mime_type))
338}
339
340fn split_data_uri(
341 src: message::DocumentSourceKind,
342) -> Result<(Option<String>, Option<String>), message::MessageError> {
343 match src {
344 message::DocumentSourceKind::Url(uri) => Ok((None, Some(uri))),
345 message::DocumentSourceKind::Base64(data) => Ok((Some(data), None)),
346 message::DocumentSourceKind::String(data) => {
347 Ok((Some(BASE64_STANDARD.encode(data.as_bytes())), None))
348 }
349 message::DocumentSourceKind::Raw(data) => Ok((Some(BASE64_STANDARD.encode(data)), None)),
350 message::DocumentSourceKind::FileId(_) => Err(message::MessageError::ConversionError(
351 "Provider file IDs are not supported for Gemini Interactions inputs".to_string(),
352 )),
353 message::DocumentSourceKind::Unknown => Err(message::MessageError::ConversionError(
354 "Unknown content source".to_string(),
355 )),
356 }
357}
358
359pub mod interactions_api_types {
367 use super::{media_parts, split_data_uri};
368 use crate::completion::Usage;
369 use crate::error::EncodeError;
370 use crate::message::{self, MimeType};
371 use base64::{Engine, prelude::BASE64_STANDARD};
372 use serde::{Deserialize, Serialize};
373 use serde_json::Value;
374
375 #[derive(Debug, Deserialize, Serialize, Default, Clone)]
377 #[serde(rename_all = "snake_case")]
378 pub struct AdditionalParameters {
379 pub agent: Option<String>,
380 pub agent_config: Option<AgentConfig>,
381 pub background: Option<bool>,
382 pub generation_config: Option<GenerationConfig>,
383 pub previous_interaction_id: Option<String>,
384 pub response_modalities: Option<Vec<ResponseModality>>,
385 pub response_format: Option<Value>,
386 pub response_mime_type: Option<String>,
387 pub store: Option<bool>,
388 pub stream: Option<bool>,
389 pub system_instruction: Option<String>,
390 pub tools: Option<Vec<Tool>>,
391 #[serde(flatten, skip_serializing_if = "Option::is_none")]
392 pub additional_params: Option<Value>,
393 }
394
395 #[derive(Debug, Deserialize, Serialize, Clone)]
397 #[serde(rename_all = "snake_case")]
398 pub struct CreateInteractionRequest {
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub model: Option<String>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub agent: Option<String>,
403 pub input: InteractionInput,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub system_instruction: Option<String>,
406 #[serde(skip_serializing_if = "Option::is_none")]
407 pub tools: Option<Vec<Tool>>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 pub response_format: Option<Value>,
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub response_mime_type: Option<String>,
412 #[serde(skip_serializing_if = "Option::is_none")]
413 pub stream: Option<bool>,
414 #[serde(skip_serializing_if = "Option::is_none")]
415 pub store: Option<bool>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 pub background: Option<bool>,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub generation_config: Option<GenerationConfig>,
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub agent_config: Option<AgentConfig>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub response_modalities: Option<Vec<ResponseModality>>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub previous_interaction_id: Option<String>,
426 #[serde(flatten, skip_serializing_if = "Option::is_none")]
427 pub additional_params: Option<Value>,
428 }
429
430 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
432 #[serde(rename_all = "snake_case")]
433 pub struct Interaction {
434 #[serde(default)]
435 pub id: String,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub model: Option<String>,
438 #[serde(skip_serializing_if = "Option::is_none")]
439 pub agent: Option<String>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub status: Option<InteractionStatus>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub object: Option<String>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub created: Option<String>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub updated: Option<String>,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub role: Option<String>,
450 #[serde(default)]
451 pub steps: Vec<Step>,
452 #[serde(skip_serializing_if = "Option::is_none")]
453 pub usage: Option<InteractionUsage>,
454 #[serde(skip_serializing_if = "Option::is_none")]
455 pub system_instruction: Option<String>,
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub tools: Option<Vec<Tool>>,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 pub background: Option<bool>,
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub response_modalities: Option<Vec<ResponseModality>>,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub response_format: Option<Value>,
464 #[serde(skip_serializing_if = "Option::is_none")]
465 pub response_mime_type: Option<String>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub previous_interaction_id: Option<String>,
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub input: Option<InteractionInput>,
470 }
471
472 impl From<&Interaction> for Usage {
473 fn from(value: &Interaction) -> Usage {
474 value.usage.as_ref().map(Usage::from).unwrap_or_default()
475 }
476 }
477
478 impl From<Interaction> for Usage {
479 fn from(value: Interaction) -> Usage {
480 (&value).into()
481 }
482 }
483
484 #[derive(Clone, Debug)]
487 pub struct Exchange<C, R> {
488 pub call_id: Option<String>,
490 pub calls: Vec<C>,
492 pub results: Vec<R>,
494 }
495
496 impl<C, R> Default for Exchange<C, R> {
497 fn default() -> Self {
498 Self {
499 call_id: None,
500 calls: Vec::new(),
501 results: Vec::new(),
502 }
503 }
504 }
505
506 trait ExchangeCall {
508 fn id(&self) -> Option<&str>;
509 }
510
511 trait ExchangeResult {
513 fn call_id(&self) -> Option<&str>;
514 }
515
516 macro_rules! impl_exchange_ids {
517 ($call:ty, $result:ty) => {
518 impl ExchangeCall for $call {
519 fn id(&self) -> Option<&str> {
520 self.id.as_deref()
521 }
522 }
523 impl ExchangeResult for $result {
524 fn call_id(&self) -> Option<&str> {
525 self.call_id.as_deref()
526 }
527 }
528 };
529 }
530
531 impl_exchange_ids!(GoogleSearchCallContent, GoogleSearchResultContent);
532 impl_exchange_ids!(UrlContextCallContent, UrlContextResultContent);
533 impl_exchange_ids!(CodeExecutionCallContent, CodeExecutionResultContent);
534
535 fn pair_exchanges<C, R>(
540 contents: &[Content],
541 as_call: impl Fn(&Content) -> Option<&C>,
542 as_result: impl Fn(&Content) -> Option<&R>,
543 ) -> Vec<Exchange<C, R>>
544 where
545 C: Clone + ExchangeCall,
546 R: Clone + ExchangeResult,
547 {
548 let mut exchanges: Vec<Exchange<C, R>> = Vec::new();
549 let mut last_call_index: Option<usize> = None;
550 let position_of = |exchanges: &[Exchange<C, R>], call_id: &str| {
551 exchanges
552 .iter()
553 .position(|exchange| exchange.call_id.as_deref() == Some(call_id))
554 };
555
556 for content in contents {
557 if let Some(call) = as_call(content) {
558 let index = match call.id() {
559 Some(call_id) => match position_of(&exchanges, call_id) {
560 Some(index) => {
561 if let Some(exchange) = exchanges.get_mut(index) {
562 exchange.calls.push(call.clone());
563 }
564 index
565 }
566 None => {
567 exchanges.push(Exchange {
568 call_id: Some(call_id.to_string()),
569 calls: vec![call.clone()],
570 results: Vec::new(),
571 });
572 exchanges.len() - 1
573 }
574 },
575 None => {
576 exchanges.push(Exchange {
577 call_id: None,
578 calls: vec![call.clone()],
579 results: Vec::new(),
580 });
581 exchanges.len() - 1
582 }
583 };
584 last_call_index = Some(index);
585 } else if let Some(result) = as_result(content) {
586 if let Some(call_id) = result.call_id() {
587 if let Some(index) = position_of(&exchanges, call_id) {
588 if let Some(exchange) = exchanges.get_mut(index) {
589 exchange.results.push(result.clone());
590 }
591 } else {
592 exchanges.push(Exchange {
593 call_id: Some(call_id.to_string()),
594 calls: Vec::new(),
595 results: vec![result.clone()],
596 });
597 }
598 } else if let Some(index) = last_call_index {
599 if let Some(exchange) = exchanges.get_mut(index) {
600 exchange.results.push(result.clone());
601 }
602 } else {
603 exchanges.push(Exchange {
604 call_id: None,
605 calls: Vec::new(),
606 results: vec![result.clone()],
607 });
608 last_call_index = Some(exchanges.len() - 1);
609 }
610 }
611 }
612
613 exchanges
614 }
615
616 pub type GoogleSearchExchange = Exchange<GoogleSearchCallContent, GoogleSearchResultContent>;
618
619 impl GoogleSearchExchange {
620 pub fn queries(&self) -> Vec<String> {
622 self.calls
623 .iter()
624 .filter_map(|call| call.arguments.as_ref()?.queries.as_ref())
625 .flatten()
626 .cloned()
627 .collect()
628 }
629
630 pub fn result_items(&self) -> Vec<GoogleSearchResult> {
632 self.results
633 .iter()
634 .filter_map(|result| result.result.as_ref())
635 .flatten()
636 .cloned()
637 .collect()
638 }
639 }
640
641 pub type UrlContextExchange = Exchange<UrlContextCallContent, UrlContextResultContent>;
643
644 impl UrlContextExchange {
645 pub fn urls(&self) -> Vec<String> {
647 self.calls
648 .iter()
649 .filter_map(|call| call.arguments.as_ref()?.urls.as_ref())
650 .flatten()
651 .cloned()
652 .collect()
653 }
654
655 pub fn result_items(&self) -> Vec<UrlContextResult> {
657 self.results
658 .iter()
659 .filter_map(|result| result.result.as_ref())
660 .flatten()
661 .cloned()
662 .collect()
663 }
664 }
665
666 pub type CodeExecutionExchange = Exchange<CodeExecutionCallContent, CodeExecutionResultContent>;
668
669 impl CodeExecutionExchange {
670 pub fn code_snippets(&self) -> Vec<String> {
672 self.calls
673 .iter()
674 .filter_map(|call| call.arguments.as_ref()?.code.clone())
675 .collect()
676 }
677
678 pub fn outputs(&self) -> Vec<String> {
680 self.results
681 .iter()
682 .filter_map(|result| result.result.clone())
683 .collect()
684 }
685 }
686
687 macro_rules! interaction_exchange_accessors {
691 (
692 $tool:literal, $exchange:ty, $call_variant:ident, $result_variant:ident,
693 $exchanges_fn:ident, $call_contents_fn:ident -> $call_ty:ty,
694 $result_contents_fn:ident -> $result_ty:ty,
695 $($flat_doc:literal $flat_fn:ident => $method:ident -> $flat_ty:ty),* $(,)?
696 ) => {
697 #[doc = concat!("Groups ", $tool, " tool calls and results by call_id.")]
698 pub fn $exchanges_fn(&self) -> Vec<$exchange> {
702 pair_exchanges(
703 &self.output_contents(),
704 |content| match content {
705 Content::$call_variant(call) => Some(call),
706 _ => None,
707 },
708 |content| match content {
709 Content::$result_variant(result) => Some(result),
710 _ => None,
711 },
712 )
713 }
714
715 #[doc = concat!("Collects ", $tool, " tool call contents from the interaction outputs.")]
716 pub fn $call_contents_fn(&self) -> Vec<$call_ty> {
717 self.$exchanges_fn()
718 .into_iter()
719 .flat_map(|exchange| exchange.calls)
720 .collect()
721 }
722
723 #[doc = concat!("Collects ", $tool, " result contents from the interaction outputs.")]
724 pub fn $result_contents_fn(&self) -> Vec<$result_ty> {
725 self.$exchanges_fn()
726 .into_iter()
727 .flat_map(|exchange| exchange.results)
728 .collect()
729 }
730
731 $(
732 #[doc = $flat_doc]
733 pub fn $flat_fn(&self) -> Vec<$flat_ty> {
734 self.$exchanges_fn()
735 .into_iter()
736 .flat_map(|exchange| exchange.$method())
737 .collect()
738 }
739 )*
740 };
741 }
742
743 impl Interaction {
744 pub(crate) fn output_contents(&self) -> Vec<Content> {
745 self.steps.iter().flat_map(Step::output_contents).collect()
746 }
747
748 interaction_exchange_accessors!(
749 "Google Search", GoogleSearchExchange, GoogleSearchCall, GoogleSearchResult,
750 google_search_exchanges,
751 google_search_call_contents -> GoogleSearchCallContent,
752 google_search_result_contents -> GoogleSearchResultContent,
753 "Collects all Google Search queries from tool calls in the outputs."
754 google_search_queries => queries -> String,
755 "Collects all Google Search result entries from tool results in the outputs."
756 google_search_results => result_items -> GoogleSearchResult,
757 );
758
759 interaction_exchange_accessors!(
760 "URL context", UrlContextExchange, UrlContextCall, UrlContextResult,
761 url_context_exchanges,
762 url_context_call_contents -> UrlContextCallContent,
763 url_context_result_contents -> UrlContextResultContent,
764 "Collects all URLs from URL context tool calls in the outputs."
765 url_context_urls => urls -> String,
766 "Collects all URL context result entries from tool results in the outputs."
767 url_context_results => result_items -> UrlContextResult,
768 );
769
770 interaction_exchange_accessors!(
771 "code execution", CodeExecutionExchange, CodeExecutionCall, CodeExecutionResult,
772 code_execution_exchanges,
773 code_execution_call_contents -> CodeExecutionCallContent,
774 code_execution_result_contents -> CodeExecutionResultContent,
775 "Collects all code snippets from code execution calls in the outputs."
776 code_execution_snippets => code_snippets -> String,
777 "Collects all code execution outputs from tool results in the outputs."
778 code_execution_outputs => outputs -> String,
779 );
780
781 pub fn text_with_inline_citations(&self) -> Option<String> {
783 let text = self
784 .output_contents()
785 .iter()
786 .filter_map(|content| match content {
787 Content::Text(text) => Some(text.with_inline_citations()),
788 _ => None,
789 })
790 .collect::<Vec<_>>()
791 .join("\n");
792
793 if text.is_empty() { None } else { Some(text) }
794 }
795
796 pub fn is_terminal(&self) -> bool {
798 self.status
799 .as_ref()
800 .is_some_and(InteractionStatus::is_terminal)
801 }
802
803 pub fn is_completed(&self) -> bool {
805 matches!(self.status, Some(InteractionStatus::Completed))
806 }
807 }
808
809 #[derive(Clone, Debug, Deserialize, Serialize)]
811 #[serde(rename_all = "snake_case")]
812 pub enum InteractionStatus {
813 InProgress,
814 RequiresAction,
815 Incomplete,
816 BudgetExceeded,
817 Completed,
818 Failed,
819 Cancelled,
820 #[serde(untagged)]
822 Unknown(String),
823 }
824
825 impl InteractionStatus {
826 pub fn is_terminal(&self) -> bool {
830 !matches!(self, InteractionStatus::InProgress)
831 }
832
833 pub fn as_wire_str(&self) -> &str {
840 match self {
841 Self::InProgress => "in_progress",
842 Self::RequiresAction => "requires_action",
843 Self::Incomplete => "incomplete",
844 Self::BudgetExceeded => "budget_exceeded",
845 Self::Completed => "completed",
846 Self::Failed => "failed",
847 Self::Cancelled => "cancelled",
848 Self::Unknown(status) => status,
849 }
850 }
851 }
852
853 pub(crate) fn map_interaction_status(
856 status: &InteractionStatus,
857 ) -> crate::completion::FinishReason {
858 match status {
859 InteractionStatus::Completed => crate::completion::FinishReason::Stop,
860 InteractionStatus::RequiresAction => crate::completion::FinishReason::ToolCalls,
861 InteractionStatus::BudgetExceeded => crate::completion::FinishReason::Length,
862 other => crate::completion::FinishReason::Other(other.as_wire_str().to_owned()),
863 }
864 }
865
866 #[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)]
868 #[serde(rename_all = "snake_case")]
869 pub struct InteractionUsage {
870 #[serde(skip_serializing_if = "Option::is_none")]
871 pub total_input_tokens: Option<u64>,
872 #[serde(skip_serializing_if = "Option::is_none")]
873 pub total_output_tokens: Option<u64>,
874 #[serde(skip_serializing_if = "Option::is_none")]
875 pub total_tokens: Option<u64>,
876 #[serde(default, skip_serializing_if = "Option::is_none")]
878 pub total_cached_tokens: Option<u64>,
879 #[serde(default, skip_serializing_if = "Option::is_none")]
881 pub total_thought_tokens: Option<u64>,
882 #[serde(default, skip_serializing_if = "Option::is_none")]
884 pub total_tool_use_tokens: Option<u64>,
885 }
886
887 impl From<&InteractionUsage> for Usage {
891 fn from(value: &InteractionUsage) -> Usage {
892 let input_tokens = value
893 .total_input_tokens
894 .map(|input| input + value.total_tool_use_tokens.unwrap_or(0));
895 let output_tokens = value
896 .total_output_tokens
897 .map(|output| output + value.total_thought_tokens.unwrap_or(0));
898 Usage {
899 input_tokens,
900 output_tokens,
901 cached_input_tokens: value.total_cached_tokens,
902 reasoning_tokens: value.total_thought_tokens,
903 tool_use_prompt_tokens: value.total_tool_use_tokens,
904 total_tokens: input_tokens
905 .zip(output_tokens)
906 .map(|(input, output)| input + output),
907 cache_creation_input_tokens: None,
908 }
909 }
910 }
911
912 impl From<InteractionUsage> for Usage {
913 fn from(value: InteractionUsage) -> Usage {
914 (&value).into()
915 }
916 }
917
918 #[derive(Clone, Debug, Deserialize, Serialize)]
920 #[serde(untagged)]
921 pub enum InteractionInput {
922 Text(String),
923 Content(Content),
924 Steps(Vec<Step>),
925 Contents(Vec<Content>),
926 }
927
928 #[derive(Clone, Debug, Deserialize, Serialize)]
930 #[serde(tag = "type", rename_all = "snake_case")]
931 pub enum Step {
932 UserInput {
934 #[serde(default)]
935 content: Vec<Content>,
936 },
937 ModelOutput {
938 #[serde(default)]
939 content: Vec<Content>,
940 },
941 Thought(ThoughtContent),
942 FunctionCall(FunctionCallContent),
943 FunctionResult(FunctionResultContent),
944 CodeExecutionCall(CodeExecutionCallContent),
945 CodeExecutionResult(CodeExecutionResultContent),
946 UrlContextCall(UrlContextCallContent),
947 UrlContextResult(UrlContextResultContent),
948 GoogleSearchCall(GoogleSearchCallContent),
949 GoogleSearchResult(GoogleSearchResultContent),
950 McpServerToolCall(McpServerToolCallContent),
951 McpServerToolResult(McpServerToolResultContent),
952 FileSearchResult(FileSearchResultContent),
953 }
954
955 impl Step {
956 fn output_contents(&self) -> Vec<Content> {
957 match self {
958 Step::UserInput { .. } => Vec::new(),
959 Step::ModelOutput { content } => content.clone(),
960 Step::Thought(content) => vec![Content::Thought(content.clone())],
961 Step::FunctionCall(content) => vec![Content::FunctionCall(content.clone())],
962 Step::FunctionResult(content) => vec![Content::FunctionResult(content.clone())],
963 Step::CodeExecutionCall(content) => {
964 vec![Content::CodeExecutionCall(content.clone())]
965 }
966 Step::CodeExecutionResult(content) => {
967 vec![Content::CodeExecutionResult(content.clone())]
968 }
969 Step::UrlContextCall(content) => vec![Content::UrlContextCall(content.clone())],
970 Step::UrlContextResult(content) => {
971 vec![Content::UrlContextResult(content.clone())]
972 }
973 Step::GoogleSearchCall(content) => {
974 vec![Content::GoogleSearchCall(content.clone())]
975 }
976 Step::GoogleSearchResult(content) => {
977 vec![Content::GoogleSearchResult(content.clone())]
978 }
979 Step::McpServerToolCall(content) => {
980 vec![Content::McpServerToolCall(content.clone())]
981 }
982 Step::McpServerToolResult(content) => {
983 vec![Content::McpServerToolResult(content.clone())]
984 }
985 Step::FileSearchResult(content) => {
986 vec![Content::FileSearchResult(content.clone())]
987 }
988 }
989 }
990 }
991
992 impl Step {
993 pub(crate) fn from_message(
997 message: crate::completion::Message,
998 ) -> Result<Vec<Self>, message::MessageError> {
999 match message {
1000 crate::completion::Message::System { content } => Ok(vec![Self::UserInput {
1001 content: vec![Content::Text(TextContent {
1002 text: content,
1003 annotations: None,
1004 })],
1005 }]),
1006 crate::completion::Message::User { content } => {
1007 let contents = content
1008 .into_iter()
1009 .map(Content::try_from)
1010 .collect::<Result<Vec<_>, _>>()?;
1011 Ok(Self::split(contents, |content| Self::UserInput { content }))
1012 }
1013 crate::completion::Message::Assistant { content, .. } => {
1014 let contents = content
1015 .into_iter()
1016 .filter(|part| match part {
1018 crate::message::AssistantContent::Reasoning(reasoning) => {
1019 reasoning.open(&super::ISSUER).is_some()
1020 }
1021 _ => true,
1022 })
1023 .map(Content::try_from)
1024 .collect::<Result<Vec<_>, _>>()?;
1025 Ok(Self::split(contents, |content| Self::ModelOutput {
1026 content,
1027 }))
1028 }
1029 }
1030 }
1031
1032 fn split(contents: Vec<Content>, group: impl Fn(Vec<Content>) -> Self) -> Vec<Self> {
1035 let mut steps = Vec::new();
1036 let mut run: Vec<Content> = Vec::new();
1037 for content in contents {
1038 let own = match content {
1039 Content::Thought(thought) => Some(Self::Thought(thought)),
1040 Content::FunctionCall(call) => Some(Self::FunctionCall(call)),
1041 Content::FunctionResult(result) => Some(Self::FunctionResult(result)),
1042 Content::CodeExecutionCall(call) => Some(Self::CodeExecutionCall(call)),
1043 Content::CodeExecutionResult(result) => Some(Self::CodeExecutionResult(result)),
1044 Content::UrlContextCall(call) => Some(Self::UrlContextCall(call)),
1045 Content::UrlContextResult(result) => Some(Self::UrlContextResult(result)),
1046 Content::GoogleSearchCall(call) => Some(Self::GoogleSearchCall(call)),
1047 Content::GoogleSearchResult(result) => Some(Self::GoogleSearchResult(result)),
1048 Content::McpServerToolCall(call) => Some(Self::McpServerToolCall(call)),
1049 Content::McpServerToolResult(result) => Some(Self::McpServerToolResult(result)),
1050 Content::FileSearchResult(result) => Some(Self::FileSearchResult(result)),
1051 grouped @ (Content::Text(_)
1052 | Content::Image(_)
1053 | Content::Audio(_)
1054 | Content::Document(_)
1055 | Content::Video(_)) => {
1056 run.push(grouped);
1057 None
1058 }
1059 };
1060 if let Some(step) = own {
1061 if !run.is_empty() {
1062 steps.push(group(std::mem::take(&mut run)));
1063 }
1064 steps.push(step);
1065 }
1066 }
1067 if !run.is_empty() {
1068 steps.push(group(run));
1069 }
1070 steps
1071 }
1072 }
1073
1074 #[derive(Clone, Debug, Deserialize, Serialize)]
1076 pub struct Annotation {
1077 #[serde(skip_serializing_if = "Option::is_none")]
1078 pub start_index: Option<i64>,
1079 #[serde(skip_serializing_if = "Option::is_none")]
1080 pub end_index: Option<i64>,
1081 #[serde(skip_serializing_if = "Option::is_none")]
1082 pub source: Option<String>,
1083 }
1084
1085 #[derive(Clone, Debug)]
1087 pub struct Citation {
1088 pub start_index: usize,
1089 pub end_index: usize,
1090 pub source: String,
1091 }
1092
1093 #[derive(Clone, Debug, Deserialize, Serialize)]
1095 pub struct TextContent {
1096 pub text: String,
1097 #[serde(skip_serializing_if = "Option::is_none")]
1098 pub annotations: Option<Vec<Annotation>>,
1099 }
1100
1101 impl TextContent {
1102 pub fn citations(&self) -> Vec<Citation> {
1104 let mut citations = Vec::new();
1105 let Some(annotations) = self.annotations.as_ref() else {
1106 return citations;
1107 };
1108
1109 for annotation in annotations {
1110 let (Some(start), Some(end), Some(source)) = (
1111 annotation.start_index,
1112 annotation.end_index,
1113 annotation.source.as_ref(),
1114 ) else {
1115 continue;
1116 };
1117
1118 if start < 0 || end < 0 {
1119 continue;
1120 }
1121 let start = start as usize;
1122 let end = end as usize;
1123 if end <= start || end > self.text.len() {
1124 continue;
1125 }
1126 if !self.text.is_char_boundary(start) || !self.text.is_char_boundary(end) {
1127 continue;
1128 }
1129
1130 citations.push(Citation {
1131 start_index: start,
1132 end_index: end,
1133 source: source.clone(),
1134 });
1135 }
1136
1137 citations.sort_by(|a, b| {
1138 a.start_index
1139 .cmp(&b.start_index)
1140 .then_with(|| a.end_index.cmp(&b.end_index))
1141 });
1142
1143 citations
1144 }
1145
1146 pub fn with_inline_citations(&self) -> String {
1148 let citations = self.citations();
1149 if citations.is_empty() {
1150 return self.text.clone();
1151 }
1152
1153 let mut source_order = Vec::new();
1154 for citation in &citations {
1155 if !source_order.contains(&citation.source) {
1156 source_order.push(citation.source.clone());
1157 }
1158 }
1159
1160 let mut inserts = citations
1161 .iter()
1162 .map(|citation| {
1163 let index = source_order
1164 .iter()
1165 .position(|source| source == &citation.source)
1166 .map_or(0, |idx| idx + 1);
1167 (
1168 citation.start_index,
1169 citation.end_index,
1170 index,
1171 &citation.source,
1172 )
1173 })
1174 .collect::<Vec<_>>();
1175
1176 inserts.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| b.0.cmp(&a.0)));
1177
1178 let mut text = self.text.clone();
1179 for (_, end, index, source) in inserts {
1180 if index == 0 {
1181 continue;
1182 }
1183 let citation = format!("[{index}]({source})");
1184 text.insert_str(end, &citation);
1185 }
1186
1187 text
1188 }
1189 }
1190
1191 #[derive(Clone, Debug, Deserialize, Serialize)]
1193 pub struct ImageContent {
1194 #[serde(skip_serializing_if = "Option::is_none")]
1195 pub data: Option<String>,
1196 #[serde(skip_serializing_if = "Option::is_none")]
1197 pub uri: Option<String>,
1198 #[serde(skip_serializing_if = "Option::is_none")]
1199 pub mime_type: Option<String>,
1200 #[serde(skip_serializing_if = "Option::is_none")]
1201 pub resolution: Option<MediaResolution>,
1202 }
1203
1204 #[derive(Clone, Debug, Deserialize, Serialize)]
1206 pub struct AudioContent {
1207 #[serde(skip_serializing_if = "Option::is_none")]
1208 pub data: Option<String>,
1209 #[serde(skip_serializing_if = "Option::is_none")]
1210 pub uri: Option<String>,
1211 #[serde(skip_serializing_if = "Option::is_none")]
1212 pub mime_type: Option<String>,
1213 }
1214
1215 #[derive(Clone, Debug, Deserialize, Serialize)]
1217 pub struct DocumentContent {
1218 #[serde(skip_serializing_if = "Option::is_none")]
1219 pub data: Option<String>,
1220 #[serde(skip_serializing_if = "Option::is_none")]
1221 pub uri: Option<String>,
1222 #[serde(skip_serializing_if = "Option::is_none")]
1223 pub mime_type: Option<String>,
1224 }
1225
1226 #[derive(Clone, Debug, Deserialize, Serialize)]
1228 pub struct VideoContent {
1229 #[serde(skip_serializing_if = "Option::is_none")]
1230 pub data: Option<String>,
1231 #[serde(skip_serializing_if = "Option::is_none")]
1232 pub uri: Option<String>,
1233 #[serde(skip_serializing_if = "Option::is_none")]
1234 pub mime_type: Option<String>,
1235 #[serde(skip_serializing_if = "Option::is_none")]
1236 pub resolution: Option<MediaResolution>,
1237 }
1238
1239 #[derive(Clone, Debug, Deserialize, Serialize)]
1241 pub struct ThoughtContent {
1242 #[serde(skip_serializing_if = "Option::is_none")]
1243 pub signature: Option<String>,
1244 #[serde(skip_serializing_if = "Option::is_none")]
1245 pub summary: Option<Vec<ThoughtSummaryContent>>,
1246 }
1247
1248 #[derive(Clone, Debug, Deserialize, Serialize)]
1250 #[serde(tag = "type", rename_all = "snake_case")]
1251 pub enum ThoughtSummaryContent {
1252 Text(TextContent),
1253 Image(ImageContent),
1254 }
1255
1256 #[derive(Clone, Debug, Deserialize, Serialize)]
1258 pub struct FunctionCallContent {
1259 #[serde(skip_serializing_if = "Option::is_none")]
1260 pub name: Option<String>,
1261 #[serde(skip_serializing_if = "Option::is_none")]
1262 pub arguments: Option<Value>,
1263 #[serde(skip_serializing_if = "Option::is_none")]
1264 pub id: Option<String>,
1265 }
1266
1267 #[derive(Clone, Debug, Deserialize, Serialize)]
1269 pub struct FunctionResultContent {
1270 #[serde(skip_serializing_if = "Option::is_none")]
1271 pub name: Option<String>,
1272 #[serde(skip_serializing_if = "Option::is_none")]
1273 pub is_error: Option<bool>,
1274 #[serde(skip_serializing_if = "Option::is_none")]
1275 pub result: Option<Value>,
1276 #[serde(skip_serializing_if = "Option::is_none")]
1277 pub call_id: Option<String>,
1278 }
1279
1280 #[derive(Clone, Debug, Deserialize, Serialize)]
1282 pub struct CodeExecutionCallArguments {
1283 #[serde(skip_serializing_if = "Option::is_none")]
1284 pub language: Option<String>,
1285 #[serde(skip_serializing_if = "Option::is_none")]
1286 pub code: Option<String>,
1287 }
1288
1289 #[derive(Clone, Debug, Deserialize, Serialize)]
1291 pub struct CodeExecutionCallContent {
1292 #[serde(skip_serializing_if = "Option::is_none")]
1293 pub arguments: Option<CodeExecutionCallArguments>,
1294 #[serde(skip_serializing_if = "Option::is_none")]
1295 pub id: Option<String>,
1296 }
1297
1298 #[derive(Clone, Debug, Deserialize, Serialize)]
1300 pub struct CodeExecutionResultContent {
1301 #[serde(skip_serializing_if = "Option::is_none")]
1302 pub result: Option<String>,
1303 #[serde(skip_serializing_if = "Option::is_none")]
1304 pub is_error: Option<bool>,
1305 #[serde(skip_serializing_if = "Option::is_none")]
1306 pub signature: Option<String>,
1307 #[serde(skip_serializing_if = "Option::is_none")]
1308 pub call_id: Option<String>,
1309 }
1310
1311 #[derive(Clone, Debug, Deserialize, Serialize)]
1313 pub struct UrlContextCallArguments {
1314 #[serde(skip_serializing_if = "Option::is_none")]
1315 pub urls: Option<Vec<String>>,
1316 }
1317
1318 #[derive(Clone, Debug, Deserialize, Serialize)]
1320 pub struct UrlContextCallContent {
1321 #[serde(skip_serializing_if = "Option::is_none")]
1322 pub arguments: Option<UrlContextCallArguments>,
1323 #[serde(skip_serializing_if = "Option::is_none")]
1324 pub id: Option<String>,
1325 }
1326
1327 #[derive(Clone, Debug, Deserialize, Serialize)]
1329 pub struct UrlContextResult {
1330 #[serde(skip_serializing_if = "Option::is_none")]
1331 pub url: Option<String>,
1332 #[serde(skip_serializing_if = "Option::is_none")]
1333 pub status: Option<String>,
1334 }
1335
1336 #[derive(Clone, Debug, Deserialize, Serialize)]
1338 pub struct UrlContextResultContent {
1339 #[serde(skip_serializing_if = "Option::is_none")]
1340 pub signature: Option<String>,
1341 #[serde(skip_serializing_if = "Option::is_none")]
1342 pub result: Option<Vec<UrlContextResult>>,
1343 #[serde(skip_serializing_if = "Option::is_none")]
1344 pub is_error: Option<bool>,
1345 #[serde(skip_serializing_if = "Option::is_none")]
1346 pub call_id: Option<String>,
1347 }
1348
1349 #[derive(Clone, Debug, Deserialize, Serialize)]
1351 pub struct GoogleSearchCallArguments {
1352 #[serde(skip_serializing_if = "Option::is_none")]
1353 pub queries: Option<Vec<String>>,
1354 }
1355
1356 #[derive(Clone, Debug, Deserialize, Serialize)]
1358 pub struct GoogleSearchCallContent {
1359 #[serde(skip_serializing_if = "Option::is_none")]
1360 pub arguments: Option<GoogleSearchCallArguments>,
1361 #[serde(skip_serializing_if = "Option::is_none")]
1362 pub id: Option<String>,
1363 }
1364
1365 #[derive(Clone, Debug, Deserialize, Serialize)]
1367 pub struct GoogleSearchResult {
1368 #[serde(skip_serializing_if = "Option::is_none")]
1369 pub url: Option<String>,
1370 #[serde(skip_serializing_if = "Option::is_none")]
1371 pub title: Option<String>,
1372 #[serde(skip_serializing_if = "Option::is_none")]
1373 pub rendered_content: Option<String>,
1374 }
1375
1376 #[derive(Clone, Debug, Deserialize, Serialize)]
1378 pub struct GoogleSearchResultContent {
1379 #[serde(skip_serializing_if = "Option::is_none")]
1380 pub signature: Option<String>,
1381 #[serde(skip_serializing_if = "Option::is_none")]
1382 pub result: Option<Vec<GoogleSearchResult>>,
1383 #[serde(skip_serializing_if = "Option::is_none")]
1384 pub is_error: Option<bool>,
1385 #[serde(skip_serializing_if = "Option::is_none")]
1386 pub call_id: Option<String>,
1387 }
1388
1389 #[derive(Clone, Debug, Deserialize, Serialize)]
1391 pub struct McpServerToolCallContent {
1392 #[serde(skip_serializing_if = "Option::is_none")]
1393 pub name: Option<String>,
1394 #[serde(skip_serializing_if = "Option::is_none")]
1395 pub server_name: Option<String>,
1396 #[serde(skip_serializing_if = "Option::is_none")]
1397 pub arguments: Option<Value>,
1398 #[serde(skip_serializing_if = "Option::is_none")]
1399 pub id: Option<String>,
1400 }
1401
1402 #[derive(Clone, Debug, Deserialize, Serialize)]
1404 pub struct McpServerToolResultContent {
1405 #[serde(skip_serializing_if = "Option::is_none")]
1406 pub name: Option<String>,
1407 #[serde(skip_serializing_if = "Option::is_none")]
1408 pub server_name: Option<String>,
1409 #[serde(skip_serializing_if = "Option::is_none")]
1410 pub result: Option<Value>,
1411 #[serde(skip_serializing_if = "Option::is_none")]
1412 pub call_id: Option<String>,
1413 }
1414
1415 #[derive(Clone, Debug, Deserialize, Serialize)]
1417 pub struct FileSearchResult {
1418 pub title: String,
1419 pub text: String,
1420 pub file_search_store: String,
1421 }
1422
1423 #[derive(Clone, Debug, Deserialize, Serialize)]
1425 pub struct FileSearchResultContent {
1426 #[serde(skip_serializing_if = "Option::is_none")]
1427 pub result: Option<Vec<FileSearchResult>>,
1428 }
1429
1430 #[derive(Clone, Debug, Deserialize, Serialize)]
1432 #[serde(tag = "type", rename_all = "snake_case")]
1433 pub enum Content {
1434 Text(TextContent),
1435 Image(ImageContent),
1436 Audio(AudioContent),
1437 Document(DocumentContent),
1438 Video(VideoContent),
1439 Thought(ThoughtContent),
1440 FunctionCall(FunctionCallContent),
1441 FunctionResult(FunctionResultContent),
1442 CodeExecutionCall(CodeExecutionCallContent),
1443 CodeExecutionResult(CodeExecutionResultContent),
1444 UrlContextCall(UrlContextCallContent),
1445 UrlContextResult(UrlContextResultContent),
1446 GoogleSearchCall(GoogleSearchCallContent),
1447 GoogleSearchResult(GoogleSearchResultContent),
1448 McpServerToolCall(McpServerToolCallContent),
1449 McpServerToolResult(McpServerToolResultContent),
1450 FileSearchResult(FileSearchResultContent),
1451 }
1452
1453 fn rich_function_result_block(
1454 content: message::ToolResultContent,
1455 ) -> Result<Value, message::MessageError> {
1456 let content = match content {
1457 message::ToolResultContent::Text(text) => Content::Text(TextContent {
1458 text: text.text,
1459 annotations: None,
1460 }),
1461 message::ToolResultContent::Json { value } => Content::Text(TextContent {
1462 text: value.to_string(),
1463 annotations: None,
1464 }),
1465 message::ToolResultContent::Image(message::Image {
1466 data, media_type, ..
1467 }) => {
1468 let media_type = media_type.ok_or_else(|| {
1469 message::MessageError::ConversionError(
1470 "Image media type is required for Gemini Interactions tool results"
1471 .to_string(),
1472 )
1473 })?;
1474 let (data, uri) = split_data_uri(data)?;
1475
1476 Content::Image(ImageContent {
1477 data,
1478 uri,
1479 mime_type: Some(media_type.to_mime_type().to_string()),
1480 resolution: None,
1481 })
1482 }
1483 };
1484
1485 serde_json::to_value(content).map_err(|err| {
1486 message::MessageError::ConversionError(format!(
1487 "Failed to serialize Gemini Interactions tool result content: {err}"
1488 ))
1489 })
1490 }
1491
1492 impl TryFrom<message::UserContent> for Content {
1493 type Error = message::MessageError;
1494
1495 fn try_from(content: message::UserContent) -> Result<Self, Self::Error> {
1496 match content {
1497 message::UserContent::Text(message::Text { text, .. }) => {
1498 Ok(Self::Text(TextContent {
1499 text,
1500 annotations: None,
1501 }))
1502 }
1503 message::UserContent::ToolResult(tool_result) => {
1504 let call_id = tool_result.call.wire().into_owned();
1506 let name = tool_result.name;
1507
1508 let mut contents = tool_result.content.into_iter().collect::<Vec<_>>();
1509 let result = if contents.len() == 1 {
1510 let content = contents.pop().ok_or_else(|| {
1511 message::MessageError::ConversionError(
1512 "Tool result content must not be empty".to_string(),
1513 )
1514 })?;
1515
1516 match content {
1517 message::ToolResultContent::Text(text) => Value::String(text.text),
1518 message::ToolResultContent::Json { value } => match value {
1523 value @ (Value::String(_) | Value::Object(_)) => value,
1524 value @ (Value::Null
1525 | Value::Bool(_)
1526 | Value::Number(_)
1527 | Value::Array(_)) => serde_json::json!({ "result": value }),
1528 },
1529 rich_content => {
1530 Value::Array(vec![rich_function_result_block(rich_content)?])
1531 }
1532 }
1533 } else {
1534 Value::Array(
1535 contents
1536 .into_iter()
1537 .map(rich_function_result_block)
1538 .collect::<Result<Vec<_>, _>>()?,
1539 )
1540 };
1541
1542 Ok(Self::FunctionResult(FunctionResultContent {
1543 name: Some(name.into()),
1544 is_error: None,
1545 result: Some(result),
1546 call_id: Some(call_id),
1547 }))
1548 }
1549 message::UserContent::Image(message::Image {
1550 data, media_type, ..
1551 }) => {
1552 let (data, uri, mime_type) = media_parts(data, media_type, "image")?;
1553 Ok(Self::Image(ImageContent {
1554 data,
1555 uri,
1556 mime_type: Some(mime_type),
1557 resolution: None,
1558 }))
1559 }
1560 message::UserContent::Audio(message::Audio {
1561 data, media_type, ..
1562 }) => {
1563 let (data, uri, mime_type) = media_parts(data, media_type, "audio")?;
1564 Ok(Self::Audio(AudioContent {
1565 data,
1566 uri,
1567 mime_type: Some(mime_type),
1568 }))
1569 }
1570 message::UserContent::Video(message::Video {
1571 data, media_type, ..
1572 }) => {
1573 let (data, uri, mime_type) = media_parts(data, media_type, "video")?;
1574 Ok(Self::Video(VideoContent {
1575 data,
1576 uri,
1577 mime_type: Some(mime_type),
1578 resolution: None,
1579 }))
1580 }
1581 message::UserContent::Document(message::Document {
1582 data, media_type, ..
1583 }) => {
1584 let media_type = media_type.ok_or_else(|| {
1585 message::MessageError::ConversionError(
1586 "Media type for document is required for Gemini".to_string(),
1587 )
1588 })?;
1589 if matches!(media_type, message::DocumentMediaType::TXT) {
1590 let text = match data {
1591 message::DocumentSourceKind::String(text) => text,
1592 message::DocumentSourceKind::Base64(data) => {
1593 let decoded = BASE64_STANDARD.decode(data).map_err(|error| {
1594 message::MessageError::ConversionError(format!(
1595 "Failed to decode text document base64 data: {error}"
1596 ))
1597 })?;
1598 String::from_utf8(decoded).map_err(|error| {
1599 message::MessageError::ConversionError(format!(
1600 "Text document data must be UTF-8: {error}"
1601 ))
1602 })?
1603 }
1604 message::DocumentSourceKind::Raw(data) => String::from_utf8(data)
1605 .map_err(|error| {
1606 message::MessageError::ConversionError(format!(
1607 "Text document data must be UTF-8: {error}"
1608 ))
1609 })?,
1610 message::DocumentSourceKind::Url(_) => {
1611 return Err(message::MessageError::ConversionError(
1612 "Text document URLs are not supported for Gemini Interactions inputs"
1613 .to_string(),
1614 ));
1615 }
1616 message::DocumentSourceKind::FileId(_) => {
1617 return Err(message::MessageError::ConversionError(
1618 "Provider file IDs are not supported for Gemini Interactions inputs"
1619 .to_string(),
1620 ));
1621 }
1622 message::DocumentSourceKind::Unknown => {
1623 return Err(message::MessageError::ConversionError(
1624 "Unknown content source".to_string(),
1625 ));
1626 }
1627 };
1628 return Ok(Self::Text(TextContent {
1629 text,
1630 annotations: None,
1631 }));
1632 }
1633 let (data, uri, mime_type) = media_parts(data, Some(media_type), "document")?;
1634 Ok(Self::Document(DocumentContent {
1635 data,
1636 uri,
1637 mime_type: Some(mime_type),
1638 }))
1639 }
1640 }
1641 }
1642 }
1643
1644 impl TryFrom<message::AssistantContent> for Content {
1645 type Error = message::MessageError;
1646
1647 fn try_from(content: message::AssistantContent) -> Result<Self, Self::Error> {
1648 match content {
1649 message::AssistantContent::Text(message::Text { text, .. }) => {
1650 Ok(Self::Text(TextContent {
1651 text,
1652 annotations: None,
1653 }))
1654 }
1655 message::AssistantContent::ToolCall(tool_call) => {
1656 let call_id = tool_call.id.wire().into_owned();
1657 Ok(Self::FunctionCall(FunctionCallContent {
1658 name: Some(tool_call.function.name.into()),
1659 arguments: Some(tool_call.function.arguments),
1660 id: Some(call_id),
1661 }))
1662 }
1663 message::AssistantContent::Reasoning(reasoning) => {
1664 let content = reasoning
1665 .open(&super::ISSUER)
1666 .ok_or_else(|| {
1667 message::MessageError::ConversionError(
1668 "Gemini cannot replay reasoning another service issued".to_owned(),
1669 )
1670 })?
1671 .content
1672 .clone();
1673 let signature = content.iter().find_map(|part| match part {
1676 message::ReasoningContent::Text { signature, .. } => signature.clone(),
1677 message::ReasoningContent::Summary(_)
1678 | message::ReasoningContent::Encrypted(_)
1679 | message::ReasoningContent::Redacted { .. } => None,
1680 });
1681 let summary: Vec<ThoughtSummaryContent> = content
1682 .into_iter()
1683 .map(|part| match part {
1684 message::ReasoningContent::Text { text, .. }
1685 | message::ReasoningContent::Summary(text)
1686 | message::ReasoningContent::Encrypted(text) => text,
1687 message::ReasoningContent::Redacted { data } => data,
1688 })
1689 .filter(|text| !text.is_empty())
1690 .map(|text| {
1691 ThoughtSummaryContent::Text(TextContent {
1692 text,
1693 annotations: None,
1694 })
1695 })
1696 .collect();
1697
1698 Ok(Self::Thought(ThoughtContent {
1699 signature,
1700 summary: (!summary.is_empty()).then_some(summary),
1701 }))
1702 }
1703 message::AssistantContent::Image(message::Image {
1704 data, media_type, ..
1705 }) => {
1706 let media_type = media_type.ok_or_else(|| {
1707 message::MessageError::ConversionError(
1708 "Media type for image is required for Gemini".to_string(),
1709 )
1710 })?;
1711 let mime_type = media_type.to_mime_type().to_string();
1712 let (data, uri) = split_data_uri(data)?;
1713 Ok(Self::Image(ImageContent {
1714 data,
1715 uri,
1716 mime_type: Some(mime_type),
1717 resolution: None,
1718 }))
1719 }
1720 }
1721 }
1722 }
1723
1724 #[derive(Clone, Debug, Deserialize, Serialize)]
1726 #[serde(rename_all = "snake_case")]
1727 pub enum ResponseModality {
1728 Text,
1729 Image,
1730 Audio,
1731 }
1732
1733 #[derive(Clone, Debug, Deserialize, Serialize)]
1735 #[serde(rename_all = "snake_case")]
1736 pub enum ThinkingLevel {
1737 Minimal,
1738 Low,
1739 Medium,
1740 High,
1741 }
1742
1743 #[derive(Clone, Debug, Deserialize, Serialize)]
1745 #[serde(rename_all = "snake_case")]
1746 pub enum ThinkingSummaries {
1747 Auto,
1748 None,
1749 }
1750
1751 #[derive(Clone, Debug, Deserialize, Serialize)]
1753 #[serde(rename_all = "snake_case")]
1754 pub struct SpeechConfig {
1755 #[serde(skip_serializing_if = "Option::is_none")]
1756 pub voice: Option<String>,
1757 #[serde(skip_serializing_if = "Option::is_none")]
1758 pub language: Option<String>,
1759 #[serde(skip_serializing_if = "Option::is_none")]
1760 pub speaker: Option<String>,
1761 }
1762
1763 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
1765 #[serde(rename_all = "snake_case")]
1766 pub struct GenerationConfig {
1767 #[serde(skip_serializing_if = "Option::is_none")]
1768 pub temperature: Option<f64>,
1769 #[serde(skip_serializing_if = "Option::is_none")]
1770 pub top_p: Option<f64>,
1771 #[serde(skip_serializing_if = "Option::is_none")]
1772 pub seed: Option<u64>,
1773 #[serde(skip_serializing_if = "Option::is_none")]
1774 pub stop_sequences: Option<Vec<String>>,
1775 #[serde(skip_serializing_if = "Option::is_none")]
1776 pub tool_choice: Option<ToolChoice>,
1777 #[serde(skip_serializing_if = "Option::is_none")]
1778 pub thinking_level: Option<ThinkingLevel>,
1779 #[serde(skip_serializing_if = "Option::is_none")]
1780 pub thinking_summaries: Option<ThinkingSummaries>,
1781 #[serde(skip_serializing_if = "Option::is_none")]
1782 pub max_output_tokens: Option<u64>,
1783 #[serde(skip_serializing_if = "Option::is_none")]
1784 pub speech_config: Option<Vec<SpeechConfig>>,
1785 }
1786
1787 impl GenerationConfig {
1788 pub fn is_empty(&self) -> bool {
1790 self.temperature.is_none()
1791 && self.top_p.is_none()
1792 && self.seed.is_none()
1793 && self.stop_sequences.is_none()
1794 && self.tool_choice.is_none()
1795 && self.thinking_level.is_none()
1796 && self.thinking_summaries.is_none()
1797 && self.max_output_tokens.is_none()
1798 && self.speech_config.is_none()
1799 }
1800 }
1801
1802 #[derive(Clone, Debug, Deserialize, Serialize)]
1804 #[serde(untagged)]
1805 pub enum ToolChoice {
1806 Type(ToolChoiceType),
1807 Config(ToolChoiceConfig),
1808 }
1809
1810 #[derive(Clone, Debug, Deserialize, Serialize)]
1812 #[serde(rename_all = "snake_case")]
1813 pub enum ToolChoiceType {
1814 Auto,
1815 Any,
1816 None,
1817 Validated,
1818 }
1819
1820 #[derive(Clone, Debug, Deserialize, Serialize)]
1822 pub struct ToolChoiceConfig {
1823 pub allowed_tools: AllowedTools,
1824 }
1825
1826 #[derive(Clone, Debug, Deserialize, Serialize)]
1828 pub struct AllowedTools {
1829 #[serde(skip_serializing_if = "Option::is_none")]
1830 pub mode: Option<ToolChoiceType>,
1831 #[serde(skip_serializing_if = "Option::is_none")]
1832 pub tools: Option<Vec<String>>,
1833 }
1834
1835 #[derive(Clone, Debug, Deserialize, Serialize)]
1837 #[serde(tag = "type", rename_all = "snake_case")]
1838 pub enum Tool {
1839 Function(FunctionTool),
1840 GoogleSearch,
1841 CodeExecution,
1842 UrlContext,
1843 ComputerUse(ComputerUseTool),
1844 McpServer(McpServerTool),
1845 FileSearch(FileSearchTool),
1846 }
1847
1848 #[derive(Clone, Debug, Deserialize, Serialize)]
1850 pub struct FunctionTool {
1851 #[serde(skip_serializing_if = "Option::is_none")]
1852 pub name: Option<String>,
1853 #[serde(skip_serializing_if = "Option::is_none")]
1854 pub description: Option<String>,
1855 #[serde(skip_serializing_if = "Option::is_none")]
1856 pub parameters: Option<Value>,
1857 }
1858
1859 #[derive(Clone, Debug, Deserialize, Serialize)]
1861 pub struct ComputerUseTool {
1862 #[serde(skip_serializing_if = "Option::is_none")]
1863 pub environment: Option<String>,
1864 #[serde(skip_serializing_if = "Option::is_none")]
1865 pub excluded_predefined_functions: Option<Vec<String>>,
1866 }
1867
1868 #[derive(Clone, Debug, Deserialize, Serialize)]
1870 pub struct McpServerTool {
1871 #[serde(skip_serializing_if = "Option::is_none")]
1872 pub name: Option<String>,
1873 #[serde(skip_serializing_if = "Option::is_none")]
1874 pub url: Option<String>,
1875 #[serde(skip_serializing_if = "Option::is_none")]
1876 pub headers: Option<Value>,
1877 #[serde(skip_serializing_if = "Option::is_none")]
1878 pub allowed_tools: Option<AllowedTools>,
1879 }
1880
1881 #[derive(Clone, Debug, Deserialize, Serialize)]
1883 pub struct FileSearchTool {
1884 #[serde(skip_serializing_if = "Option::is_none")]
1885 pub file_search_store_names: Option<Vec<String>>,
1886 #[serde(skip_serializing_if = "Option::is_none")]
1887 pub top_k: Option<u64>,
1888 #[serde(skip_serializing_if = "Option::is_none")]
1889 pub metadata_filter: Option<String>,
1890 }
1891
1892 impl TryFrom<crate::completion::ToolDefinition> for Tool {
1893 type Error = EncodeError;
1894
1895 fn try_from(tool: crate::completion::ToolDefinition) -> Result<Self, Self::Error> {
1896 Ok(Tool::Function(FunctionTool {
1897 name: Some(tool.name),
1898 description: Some(tool.description),
1899 parameters: Some(tool.parameters),
1900 }))
1901 }
1902 }
1903
1904 impl TryFrom<message::ToolChoice> for ToolChoice {
1905 type Error = EncodeError;
1906
1907 fn try_from(tool_choice: message::ToolChoice) -> Result<Self, Self::Error> {
1908 match tool_choice {
1909 message::ToolChoice::Auto => Ok(ToolChoice::Type(ToolChoiceType::Auto)),
1910 message::ToolChoice::None => Ok(ToolChoice::Type(ToolChoiceType::None)),
1911 message::ToolChoice::Required => Ok(ToolChoice::Type(ToolChoiceType::Any)),
1912 message::ToolChoice::Specific { function_names } => {
1913 Ok(ToolChoice::Config(ToolChoiceConfig {
1914 allowed_tools: AllowedTools {
1915 mode: Some(ToolChoiceType::Validated),
1916 tools: Some(function_names),
1917 },
1918 }))
1919 }
1920 }
1921 }
1922 }
1923
1924 #[derive(Clone, Debug, Deserialize, Serialize)]
1926 #[serde(tag = "type", rename_all = "kebab-case")]
1927 pub enum AgentConfig {
1928 Dynamic,
1929 DeepResearch {
1930 #[serde(skip_serializing_if = "Option::is_none")]
1931 thinking_summaries: Option<ThinkingSummaries>,
1932 },
1933 }
1934
1935 #[derive(Clone, Debug, Deserialize, Serialize)]
1937 #[serde(rename_all = "snake_case")]
1938 pub enum MediaResolution {
1939 Low,
1940 Medium,
1941 High,
1942 UltraHigh,
1943 }
1944
1945 #[derive(Clone, Debug, Deserialize, Serialize)]
1947 #[serde(tag = "event_type")]
1948 pub enum InteractionSseEvent {
1949 #[serde(rename = "interaction.created")]
1950 InteractionCreated {
1951 interaction: Interaction,
1952 #[serde(skip_serializing_if = "Option::is_none")]
1953 event_id: Option<String>,
1954 },
1955 #[serde(rename = "interaction.completed")]
1956 InteractionCompleted {
1957 interaction: Interaction,
1958 #[serde(skip_serializing_if = "Option::is_none")]
1959 event_id: Option<String>,
1960 },
1961 #[serde(rename = "interaction.status_update")]
1962 InteractionStatusUpdate {
1963 #[serde(default)]
1965 interaction_id: String,
1966 status: InteractionStatus,
1967 #[serde(skip_serializing_if = "Option::is_none")]
1968 event_id: Option<String>,
1969 },
1970 #[serde(rename = "step.start")]
1971 StepStart {
1972 index: u32,
1973 step: Step,
1974 #[serde(skip_serializing_if = "Option::is_none")]
1975 event_id: Option<String>,
1976 },
1977 #[serde(rename = "step.delta")]
1978 StepDelta {
1979 index: u32,
1980 delta: ContentDelta,
1981 #[serde(skip_serializing_if = "Option::is_none")]
1982 event_id: Option<String>,
1983 },
1984 #[serde(rename = "step.stop")]
1985 StepStop {
1986 index: u32,
1987 #[serde(skip_serializing_if = "Option::is_none")]
1988 event_id: Option<String>,
1989 },
1990 #[serde(rename = "error")]
1991 Error {
1992 error: ErrorEvent,
1993 #[serde(skip_serializing_if = "Option::is_none")]
1994 event_id: Option<String>,
1995 },
1996 }
1997
1998 #[derive(Clone, Debug, Deserialize, Serialize)]
2000 pub struct ErrorEvent {
2001 pub code: String,
2002 pub message: String,
2003 }
2004
2005 #[derive(Clone, Debug, Deserialize, Serialize)]
2007 #[serde(tag = "type", rename_all = "snake_case")]
2008 pub enum ContentDelta {
2009 Text(TextDelta),
2010 Image(ImageContent),
2011 Audio(AudioContent),
2012 Document(DocumentContent),
2013 Video(VideoContent),
2014 ThoughtSummary(ThoughtSummaryDelta),
2015 ThoughtSignature(ThoughtSignatureDelta),
2016 FunctionCall(FunctionCallContent),
2017 ArgumentsDelta(ArgumentsDelta),
2018 FunctionResult(FunctionResultContent),
2019 CodeExecutionCall(CodeExecutionCallContent),
2020 CodeExecutionResult(CodeExecutionResultContent),
2021 UrlContextCall(UrlContextCallContent),
2022 UrlContextResult(UrlContextResultContent),
2023 GoogleSearchCall(GoogleSearchCallContent),
2024 GoogleSearchResult(GoogleSearchResultContent),
2025 McpServerToolCall(McpServerToolCallContent),
2026 McpServerToolResult(McpServerToolResultContent),
2027 FileSearchResult(FileSearchResultContent),
2028 }
2029
2030 #[derive(Clone, Debug, Deserialize, Serialize)]
2032 pub struct ArgumentsDelta {
2033 #[serde(skip_serializing_if = "Option::is_none")]
2034 pub arguments: Option<String>,
2035 }
2036
2037 #[derive(Clone, Debug, Deserialize, Serialize)]
2039 pub struct TextDelta {
2040 #[serde(skip_serializing_if = "Option::is_none")]
2041 pub text: Option<String>,
2042 #[serde(skip_serializing_if = "Option::is_none")]
2043 pub annotations: Option<Vec<Annotation>>,
2044 }
2045
2046 #[derive(Clone, Debug, Deserialize, Serialize)]
2048 pub struct ThoughtSummaryDelta {
2049 pub content: ThoughtSummaryContent,
2050 }
2051
2052 #[derive(Clone, Debug, Deserialize, Serialize)]
2054 pub struct ThoughtSignatureDelta {
2055 pub signature: String,
2056 }
2057}
2058
2059#[cfg(test)]
2060mod tests;
2061
2062#[cfg(test)]
2063mod interaction_usage_tests;