use serde::{Deserialize, Serialize};
use super::agent_config::AgentConfig;
use super::environment::Environment;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Content {
Text(TextContent),
Image(ImageContent),
Audio(AudioContent),
Document(DocumentContent),
Video(VideoContent),
}
impl Content {
pub fn text(text: impl Into<String>) -> Self {
Content::Text(TextContent { text: text.into(), annotations: Vec::new() })
}
pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Content::Image(ImageContent {
data: Some(data.into()),
mime_type: Some(mime_type.into()),
uri: None,
resolution: None,
})
}
pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Content::Audio(AudioContent {
data: Some(data.into()),
mime_type: Some(mime_type.into()),
uri: None,
sample_rate: None,
channels: None,
})
}
pub fn document(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Content::Document(DocumentContent {
data: Some(data.into()),
mime_type: Some(mime_type.into()),
uri: None,
})
}
pub fn video_uri(uri: impl Into<String>) -> Self {
Content::Video(VideoContent {
data: None,
mime_type: None,
uri: Some(uri.into()),
resolution: None,
})
}
pub fn as_text(&self) -> Option<&str> {
match self {
Content::Text(t) => Some(&t.text),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextContent {
pub text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub annotations: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImageContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AudioContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sample_rate: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub channels: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DocumentContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VideoContent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Step {
UserInput {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
content: Vec<Content>,
},
ModelOutput {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
content: Vec<Content>,
},
Thought {
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
summary: Vec<Content>,
},
FunctionCall {
id: String,
name: String,
arguments: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
},
FunctionResult {
call_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
result: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
},
#[serde(untagged)]
Other(serde_json::Value),
}
impl Step {
pub fn output_text(&self) -> Option<String> {
match self {
Step::ModelOutput { content } => {
let text: String =
content.iter().filter_map(Content::as_text).collect::<Vec<_>>().join("");
if text.is_empty() { None } else { Some(text) }
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Tool {
Function {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
parameters: Option<serde_json::Value>,
},
CodeExecution,
UrlContext,
GoogleSearch {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
search_types: Vec<String>,
},
#[serde(untagged)]
Other(serde_json::Value),
}
impl Tool {
pub fn function(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Tool::Function {
name: name.into(),
description: Some(description.into()),
parameters: Some(parameters),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ThinkingSummaries {
Auto,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoice {
Auto,
Any,
None,
Validated,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ImageConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct GenerationConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stop_sequences: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking_level: Option<crate::ThinkingLevel>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking_summaries: Option<ThinkingSummaries>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_config: Option<ImageConfig>,
}
impl GenerationConfig {
pub fn validate(&self) -> Result<(), String> {
if let Some(t) = self.temperature
&& !(0.0..=2.0).contains(&t)
{
return Err("temperature must be between 0.0 and 2.0".to_string());
}
if let Some(p) = self.top_p
&& !(0.0..=1.0).contains(&p)
{
return Err("top_p must be between 0.0 and 1.0".to_string());
}
if let Some(m) = self.max_output_tokens
&& m <= 0
{
return Err("max_output_tokens must be positive".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseFormat {
Text {
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
schema: Option<serde_json::Value>,
},
Image {
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
aspect_ratio: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
image_size: Option<String>,
},
Audio {
#[serde(default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
sample_rate: Option<i64>,
},
}
impl ResponseFormat {
pub fn json_schema(schema: serde_json::Value) -> Self {
ResponseFormat::Text {
mime_type: Some("application/json".to_string()),
schema: Some(schema),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResponseModality {
Text,
Image,
Audio,
Video,
Document,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ServiceTier {
Flex,
Standard,
Priority,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Input {
Text(String),
Content(Vec<Content>),
Steps(Vec<Step>),
}
impl Default for Input {
fn default() -> Self {
Input::Content(Vec::new())
}
}
impl From<String> for Input {
fn from(s: String) -> Self {
Input::Text(s)
}
}
impl From<&str> for Input {
fn from(s: &str) -> Self {
Input::Text(s.to_string())
}
}
impl From<Vec<Content>> for Input {
fn from(c: Vec<Content>) -> Self {
Input::Content(c)
}
}
impl From<Vec<Step>> for Input {
fn from(s: Vec<Step>) -> Self {
Input::Steps(s)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CreateInteractionRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
pub input: Input,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system_instruction: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<Tool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_format_list: Option<Vec<ResponseFormat>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub response_modalities: Vec<ResponseModality>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generation_config: Option<GenerationConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub previous_interaction_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub service_tier: Option<ServiceTier>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub environment: Option<Environment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_config: Option<AgentConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InteractionStatus {
InProgress,
RequiresAction,
Completed,
Failed,
Cancelled,
Incomplete,
BudgetExceeded,
}
impl InteractionStatus {
pub fn is_terminal(&self) -> bool {
matches!(
self,
InteractionStatus::Completed
| InteractionStatus::Failed
| InteractionStatus::Cancelled
| InteractionStatus::Incomplete
| InteractionStatus::BudgetExceeded
)
}
pub fn requires_action(&self) -> bool {
matches!(self, InteractionStatus::RequiresAction)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModalityTokens {
pub modality: ResponseModality,
pub tokens: i64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Usage {
#[serde(default)]
pub total_input_tokens: i64,
#[serde(default)]
pub total_output_tokens: i64,
#[serde(default)]
pub total_thought_tokens: i64,
#[serde(default)]
pub total_cached_tokens: i64,
#[serde(default)]
pub total_tool_use_tokens: i64,
#[serde(default)]
pub total_tokens: i64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input_tokens_by_modality: Vec<ModalityTokens>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output_tokens_by_modality: Vec<ModalityTokens>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Interaction {
#[serde(default)]
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
#[serde(default = "default_status")]
pub status: InteractionStatus,
#[serde(default)]
pub steps: Vec<Step>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updated: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub environment_id: Option<String>,
}
fn default_status() -> InteractionStatus {
InteractionStatus::InProgress
}
impl Interaction {
pub fn output_text(&self) -> Option<String> {
self.steps.iter().rev().find_map(Step::output_text)
}
pub fn pending_function_calls(&self) -> Vec<(String, String, serde_json::Value)> {
self.steps
.iter()
.filter_map(|step| match step {
Step::FunctionCall { id, name, arguments, .. } => {
Some((id.clone(), name.clone(), arguments.clone()))
}
_ => None,
})
.collect()
}
}