use std::time::Duration;
use crate::{InteractionResponse, StepDelta};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct PendingFunctionCall {
pub name: String,
pub call_id: String,
pub args: serde_json::Value,
}
impl PendingFunctionCall {
#[must_use]
pub fn new(
name: impl Into<String>,
call_id: impl Into<String>,
args: serde_json::Value,
) -> Self {
Self {
name: name.into(),
call_id: call_id.into(),
args,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum AutoFunctionStreamChunk {
Delta(StepDelta),
ExecutingFunctions {
response: InteractionResponse,
pending_calls: Vec<PendingFunctionCall>,
},
FunctionResults(Vec<FunctionExecutionResult>),
Complete(InteractionResponse),
MaxLoopsReached(InteractionResponse),
Unknown {
chunk_type: String,
data: serde_json::Value,
},
}
impl AutoFunctionStreamChunk {
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown { .. })
}
#[must_use]
pub const fn is_delta(&self) -> bool {
matches!(self, Self::Delta(_))
}
#[must_use]
pub const fn is_complete(&self) -> bool {
matches!(self, Self::Complete(_))
}
#[must_use]
pub fn unknown_chunk_type(&self) -> Option<&str> {
match self {
Self::Unknown { chunk_type, .. } => Some(chunk_type),
_ => None,
}
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
match self {
Self::Unknown { data, .. } => Some(data),
_ => None,
}
}
}
impl Serialize for AutoFunctionStreamChunk {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
match self {
Self::Delta(content) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", "delta")?;
map.serialize_entry("data", content)?;
map.end()
}
Self::ExecutingFunctions {
response,
pending_calls,
} => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", "executing_functions")?;
let data = serde_json::json!({
"response": response,
"pending_calls": pending_calls,
});
map.serialize_entry("data", &data)?;
map.end()
}
Self::FunctionResults(results) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", "function_results")?;
map.serialize_entry("data", results)?;
map.end()
}
Self::Complete(response) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", "complete")?;
map.serialize_entry("data", response)?;
map.end()
}
Self::MaxLoopsReached(response) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", "max_loops_reached")?;
map.serialize_entry("data", response)?;
map.end()
}
Self::Unknown { chunk_type, data } => {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk_type", chunk_type)?;
if !data.is_null() {
map.serialize_entry("data", data)?;
}
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for AutoFunctionStreamChunk {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
fn extract_data_field(value: &serde_json::Value, variant_name: &str) -> serde_json::Value {
match value.get("data").cloned() {
Some(d) => d,
None => {
tracing::warn!(
"AutoFunctionStreamChunk::{} is missing the 'data' field. \
This may indicate a malformed API response.",
variant_name
);
serde_json::Value::Null
}
}
}
let chunk_type = match value.get("chunk_type") {
Some(serde_json::Value::String(s)) => s.as_str(),
Some(other) => {
tracing::warn!(
"AutoFunctionStreamChunk received non-string chunk_type: {}. \
This may indicate a malformed API response.",
other
);
"<non-string chunk_type>"
}
None => {
tracing::warn!(
"AutoFunctionStreamChunk is missing required chunk_type field. \
This may indicate a malformed API response."
);
"<missing chunk_type>"
}
};
match chunk_type {
"delta" => {
let data = extract_data_field(&value, "Delta");
let delta: StepDelta = serde_json::from_value(data).map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize AutoFunctionStreamChunk::Delta data: {}",
e
))
})?;
Ok(Self::Delta(delta))
}
"executing_functions" => {
let data = extract_data_field(&value, "ExecutingFunctions");
let response = serde_json::from_value(
data.get("response")
.cloned()
.unwrap_or(serde_json::Value::Null),
)
.map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize ExecutingFunctions response: {}",
e
))
})?;
let pending_calls = serde_json::from_value(
data.get("pending_calls")
.cloned()
.unwrap_or(serde_json::json!([])),
)
.map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize ExecutingFunctions pending_calls: {}",
e
))
})?;
Ok(Self::ExecutingFunctions {
response,
pending_calls,
})
}
"function_results" => {
let data = extract_data_field(&value, "FunctionResults");
let results: Vec<FunctionExecutionResult> =
serde_json::from_value(data).map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize AutoFunctionStreamChunk::FunctionResults data: {}",
e
))
})?;
Ok(Self::FunctionResults(results))
}
"complete" => {
let data = extract_data_field(&value, "Complete");
let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize AutoFunctionStreamChunk::Complete data: {}",
e
))
})?;
Ok(Self::Complete(response))
}
"max_loops_reached" => {
let data = extract_data_field(&value, "MaxLoopsReached");
let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
serde::de::Error::custom(format!(
"Failed to deserialize AutoFunctionStreamChunk::MaxLoopsReached data: {}",
e
))
})?;
Ok(Self::MaxLoopsReached(response))
}
other => {
tracing::warn!(
"Encountered unknown AutoFunctionStreamChunk type '{}'. \
This may indicate a new API feature. \
The chunk will be preserved in the Unknown variant.",
other
);
let data = value
.get("data")
.cloned()
.unwrap_or(serde_json::Value::Null);
Ok(Self::Unknown {
chunk_type: other.to_string(),
data,
})
}
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AutoFunctionStreamEvent {
pub chunk: AutoFunctionStreamChunk,
pub event_id: Option<String>,
}
impl AutoFunctionStreamEvent {
#[must_use]
pub fn new(chunk: AutoFunctionStreamChunk, event_id: Option<String>) -> Self {
Self { chunk, event_id }
}
#[must_use]
pub const fn is_delta(&self) -> bool {
self.chunk.is_delta()
}
#[must_use]
pub const fn is_complete(&self) -> bool {
self.chunk.is_complete()
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
self.chunk.is_unknown()
}
#[must_use]
pub fn unknown_chunk_type(&self) -> Option<&str> {
self.chunk.unknown_chunk_type()
}
#[must_use]
pub fn unknown_data(&self) -> Option<&serde_json::Value> {
self.chunk.unknown_data()
}
}
impl Serialize for AutoFunctionStreamEvent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("chunk", &self.chunk)?;
if let Some(id) = &self.event_id {
map.serialize_entry("event_id", id)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for AutoFunctionStreamEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let chunk = match value.get("chunk") {
Some(chunk_value) => {
serde_json::from_value(chunk_value.clone()).map_err(serde::de::Error::custom)?
}
None => {
return Err(serde::de::Error::missing_field("chunk"));
}
};
let event_id = value
.get("event_id")
.and_then(|v| v.as_str())
.map(String::from);
Ok(Self { chunk, event_id })
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FunctionExecutionResult {
pub name: String,
pub call_id: String,
pub args: serde_json::Value,
pub result: serde_json::Value,
#[serde(with = "duration_millis")]
pub duration: Duration,
}
impl FunctionExecutionResult {
#[must_use]
pub fn new(
name: impl Into<String>,
call_id: impl Into<String>,
args: serde_json::Value,
result: serde_json::Value,
duration: Duration,
) -> Self {
Self {
name: name.into(),
call_id: call_id.into(),
args,
result,
duration,
}
}
#[must_use]
pub fn is_error(&self) -> bool {
self.result.get("error").is_some()
}
#[must_use]
pub fn is_success(&self) -> bool {
!self.is_error()
}
#[must_use]
pub fn error_message(&self) -> Option<&str> {
self.result.get("error").and_then(|v| v.as_str())
}
}
mod duration_millis {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
duration.as_millis().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(millis))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AutoFunctionResult {
pub response: InteractionResponse,
pub executions: Vec<FunctionExecutionResult>,
#[serde(default)]
pub reached_max_loops: bool,
}
impl AutoFunctionResult {
#[must_use]
pub fn all_executions_succeeded(&self) -> bool {
self.executions.iter().all(|e| e.is_success())
}
#[must_use]
pub fn failed_executions(&self) -> Vec<&FunctionExecutionResult> {
self.executions.iter().filter(|e| e.is_error()).collect()
}
}
#[derive(Clone, Debug, Default)]
pub struct AutoFunctionResultAccumulator {
executions: Vec<FunctionExecutionResult>,
}
impl AutoFunctionResultAccumulator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
#[allow(unreachable_patterns)] pub fn push(&mut self, chunk: AutoFunctionStreamChunk) -> Option<AutoFunctionResult> {
match chunk {
AutoFunctionStreamChunk::FunctionResults(results) => {
self.executions.extend(results);
None
}
AutoFunctionStreamChunk::Complete(response) => Some(AutoFunctionResult {
response,
executions: std::mem::take(&mut self.executions),
reached_max_loops: false,
}),
AutoFunctionStreamChunk::MaxLoopsReached(response) => Some(AutoFunctionResult {
response,
executions: std::mem::take(&mut self.executions),
reached_max_loops: true,
}),
AutoFunctionStreamChunk::Delta(_)
| AutoFunctionStreamChunk::ExecutingFunctions { .. } => None,
_ => None,
}
}
#[must_use]
pub fn executions(&self) -> &[FunctionExecutionResult] {
&self.executions
}
pub fn reset(&mut self) {
self.executions.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_function_execution_result() {
let result = FunctionExecutionResult::new(
"get_weather",
"call-123",
json!({"city": "Seattle"}),
json!({"temp": 20, "unit": "celsius"}),
Duration::from_millis(42),
);
assert_eq!(result.name, "get_weather");
assert_eq!(result.call_id, "call-123");
assert_eq!(result.args, json!({"city": "Seattle"}));
assert_eq!(result.result, json!({"temp": 20, "unit": "celsius"}));
assert_eq!(result.duration, Duration::from_millis(42));
}
#[test]
fn test_auto_function_stream_chunk_variants() {
let _delta = AutoFunctionStreamChunk::Delta(StepDelta::Text {
text: "Hello".to_string(),
});
let _results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult {
name: "test".to_string(),
call_id: "1".to_string(),
args: json!({}),
result: json!({"ok": true}),
duration: Duration::from_millis(10),
}]);
let _complete = AutoFunctionStreamChunk::Complete(InteractionResponse::default());
}
#[test]
fn test_function_execution_result_serialization() {
let result = FunctionExecutionResult::new(
"get_weather",
"call-456",
json!({"city": "Miami"}),
json!({"temp": 22, "conditions": "sunny"}),
Duration::from_millis(150),
);
let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
assert!(
json_str.contains("get_weather"),
"Should contain function name"
);
assert!(json_str.contains("call-456"), "Should contain call_id");
assert!(json_str.contains("sunny"), "Should contain result data");
let deserialized: FunctionExecutionResult =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
assert_eq!(deserialized, result);
}
#[test]
fn test_auto_function_stream_chunk_serialization_roundtrip() {
let delta = AutoFunctionStreamChunk::Delta(StepDelta::Text {
text: "Hello, world!".to_string(),
});
let json_str = serde_json::to_string(&delta).expect("Serialization should succeed");
assert!(json_str.contains("chunk_type"), "Should contain tag field");
assert!(json_str.contains("delta"), "Should contain variant name");
assert!(json_str.contains("Hello, world!"), "Should contain text");
let value: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(value["data"]["type"], "text");
assert_eq!(value["data"]["text"], "Hello, world!");
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
match deserialized {
AutoFunctionStreamChunk::Delta(delta) => {
assert_eq!(delta.as_text(), Some("Hello, world!"));
}
_ => panic!("Expected Delta variant"),
}
let results = AutoFunctionStreamChunk::FunctionResults(vec![
FunctionExecutionResult::new(
"get_weather",
"call-1",
json!({"city": "Tokyo"}),
json!({"temp": 20}),
Duration::from_millis(50),
),
FunctionExecutionResult::new(
"get_time",
"call-2",
json!({"timezone": "UTC"}),
json!({"time": "14:30"}),
Duration::from_millis(30),
),
]);
let json_str = serde_json::to_string(&results).expect("Serialization should succeed");
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
match deserialized {
AutoFunctionStreamChunk::FunctionResults(execs) => {
assert_eq!(execs.len(), 2);
assert_eq!(execs[0].name, "get_weather");
assert_eq!(execs[1].name, "get_time");
}
_ => panic!("Expected FunctionResults variant"),
}
let unknown_json = r#"{"chunk_type": "future_event_type", "data": {"key": "value"}}"#;
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
assert!(deserialized.is_unknown());
assert_eq!(deserialized.unknown_chunk_type(), Some("future_event_type"));
let data = deserialized.unknown_data().expect("Should have data");
assert_eq!(data["key"], "value");
let reserialized = serde_json::to_string(&deserialized).expect("Should serialize");
assert!(reserialized.contains("future_event_type"));
assert!(reserialized.contains("value"));
}
#[test]
fn test_auto_function_stream_chunk_unknown_without_data() {
let unknown_json = r#"{"chunk_type": "no_data_chunk"}"#;
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
assert!(deserialized.is_unknown());
assert_eq!(deserialized.unknown_chunk_type(), Some("no_data_chunk"));
let data = deserialized.unknown_data().expect("Should have data field");
assert!(data.is_null());
}
#[test]
fn test_auto_function_result_roundtrip() {
use crate::InteractionStatus;
let result = AutoFunctionResult {
response: crate::InteractionResponse {
id: Some("interaction-abc123".to_string()),
model: Some("gemini-3-flash-preview".to_string()),
steps: vec![
crate::Step::model_text("Based on the weather data:"),
crate::Step::model_text("Paris is 18°C and London is 15°C."),
],
status: InteractionStatus::Completed,
usage: Some(crate::UsageMetadata {
total_input_tokens: Some(50),
total_output_tokens: Some(30),
total_tokens: Some(80),
..Default::default()
}),
previous_interaction_id: Some("prev-interaction-xyz".to_string()),
..Default::default()
},
executions: vec![
FunctionExecutionResult::new(
"get_weather",
"call-001",
json!({"city": "Paris"}),
json!({"city": "Paris", "temp": 18, "unit": "celsius"}),
Duration::from_millis(120),
),
FunctionExecutionResult::new(
"get_weather",
"call-002",
json!({"city": "London"}),
json!({"city": "London", "temp": 15, "unit": "celsius"}),
Duration::from_millis(95),
),
],
reached_max_loops: false,
};
let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
assert!(
json_str.contains("interaction-abc123"),
"Should contain interaction ID"
);
assert!(
json_str.contains("gemini-3-flash-preview"),
"Should contain model name"
);
assert!(
json_str.contains("get_weather"),
"Should contain function name"
);
assert!(
json_str.contains("call-001"),
"Should contain first call_id"
);
assert!(
json_str.contains("call-002"),
"Should contain second call_id"
);
assert!(json_str.contains("Paris"), "Should contain Paris");
assert!(json_str.contains("London"), "Should contain London");
assert!(
json_str.contains("prev-interaction-xyz"),
"Should contain previous interaction ID"
);
let deserialized: AutoFunctionResult =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
assert_eq!(
deserialized.response.id.as_deref(),
Some("interaction-abc123")
);
assert_eq!(
deserialized.response.model,
Some("gemini-3-flash-preview".to_string())
);
assert_eq!(deserialized.response.status, InteractionStatus::Completed);
assert_eq!(
deserialized.response.previous_interaction_id,
Some("prev-interaction-xyz".to_string())
);
let usage = deserialized.response.usage.expect("Should have usage");
assert_eq!(usage.total_input_tokens, Some(50));
assert_eq!(usage.total_output_tokens, Some(30));
assert_eq!(usage.total_tokens, Some(80));
assert_eq!(deserialized.executions.len(), 2);
assert_eq!(deserialized.executions[0].name, "get_weather");
assert_eq!(deserialized.executions[0].call_id, "call-001");
assert_eq!(deserialized.executions[0].result["city"], "Paris");
assert_eq!(deserialized.executions[1].name, "get_weather");
assert_eq!(deserialized.executions[1].call_id, "call-002");
assert_eq!(deserialized.executions[1].result["city"], "London");
assert!(!deserialized.reached_max_loops);
}
#[test]
fn test_auto_function_result_reached_max_loops() {
use crate::InteractionStatus;
let result = AutoFunctionResult {
response: crate::InteractionResponse {
id: Some("interaction-stuck".to_string()),
model: Some("gemini-3-flash-preview".to_string()),
steps: vec![crate::Step::function_call(
"call-stuck",
"get_weather",
json!({"city": "Tokyo"}),
)],
status: InteractionStatus::Completed,
..Default::default()
},
executions: vec![FunctionExecutionResult::new(
"get_weather",
"call-1",
json!({"city": "Berlin"}),
json!({"temp": 25}),
Duration::from_millis(50),
)],
reached_max_loops: true,
};
let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
assert!(
json_str.contains("reached_max_loops"),
"Should contain reached_max_loops field"
);
assert!(json_str.contains("true"), "Should contain true value");
let deserialized: AutoFunctionResult =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
assert!(deserialized.reached_max_loops);
assert_eq!(deserialized.executions.len(), 1);
}
#[test]
fn test_auto_function_result_backwards_compatibility() {
let legacy_json = r#"{
"response": {
"id": "interaction-old",
"model": "gemini-3-flash-preview",
"steps": [],
"status": "completed"
},
"executions": []
}"#;
let deserialized: AutoFunctionResult =
serde_json::from_str(legacy_json).expect("Should deserialize legacy JSON");
assert!(
!deserialized.reached_max_loops,
"Missing field should default to false"
);
}
#[test]
fn test_max_loops_reached_chunk_roundtrip() {
use crate::InteractionStatus;
let response = crate::InteractionResponse {
id: Some("interaction-max-loops".to_string()),
model: Some("gemini-3-flash-preview".to_string()),
steps: vec![crate::Step::function_call(
"call-pending",
"stuck_function",
json!({}),
)],
status: InteractionStatus::Completed,
..Default::default()
};
let chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
assert!(
json_str.contains("max_loops_reached"),
"Should contain chunk_type"
);
assert!(
json_str.contains("interaction-max-loops"),
"Should contain response data"
);
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
match deserialized {
AutoFunctionStreamChunk::MaxLoopsReached(resp) => {
assert_eq!(resp.id.as_deref(), Some("interaction-max-loops"));
assert_eq!(resp.function_calls().len(), 1);
assert_eq!(resp.function_calls()[0].name, "stuck_function");
}
other => panic!("Expected MaxLoopsReached, got {:?}", other),
}
}
#[test]
fn test_accumulator_handles_max_loops_reached() {
use crate::InteractionStatus;
let mut accumulator = AutoFunctionResultAccumulator::new();
let results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
"test_func",
"call-1",
json!({}),
json!({"ok": true}),
Duration::from_millis(10),
)]);
assert!(
accumulator.push(results).is_none(),
"Should not complete yet"
);
assert_eq!(accumulator.executions().len(), 1);
let response = crate::InteractionResponse {
id: Some("max-loops-response".to_string()),
model: Some("gemini-3-flash-preview".to_string()),
status: InteractionStatus::Completed,
..Default::default()
};
let max_loops_chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
let result = accumulator.push(max_loops_chunk);
assert!(result.is_some(), "Should complete on MaxLoopsReached");
let result = result.unwrap();
assert!(
result.reached_max_loops,
"Should have reached_max_loops: true"
);
assert_eq!(result.executions.len(), 1);
assert_eq!(result.response.id.as_deref(), Some("max-loops-response"));
}
#[test]
fn test_auto_function_stream_event_with_event_id_roundtrip() {
let event = AutoFunctionStreamEvent::new(
AutoFunctionStreamChunk::Delta(StepDelta::Text {
text: "Hello from auto-function".to_string(),
}),
Some("evt_auto_abc123".to_string()),
);
assert!(event.is_delta());
assert!(!event.is_complete());
assert!(!event.is_unknown());
let json = serde_json::to_string(&event).expect("Serialization should succeed");
assert!(json.contains("evt_auto_abc123"), "Should have event_id");
assert!(
json.contains("Hello from auto-function"),
"Should have content"
);
let deserialized: AutoFunctionStreamEvent =
serde_json::from_str(&json).expect("Deserialization should succeed");
assert_eq!(deserialized.event_id.as_deref(), Some("evt_auto_abc123"));
assert!(deserialized.is_delta());
}
#[test]
fn test_auto_function_stream_event_without_event_id() {
let event = AutoFunctionStreamEvent::new(
AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
"weather",
"call-123",
json!({"city": "Denver"}),
json!({"temp": 72}),
Duration::from_millis(50),
)]),
None,
);
assert!(!event.is_delta());
assert!(!event.is_complete());
assert!(event.event_id.is_none());
let json = serde_json::to_string(&event).expect("Serialization should succeed");
assert!(!json.contains("event_id"), "Should not have event_id field");
assert!(json.contains("weather"), "Should have function name");
let deserialized: AutoFunctionStreamEvent =
serde_json::from_str(&json).expect("Deserialization should succeed");
assert!(deserialized.event_id.is_none());
}
#[test]
fn test_auto_function_stream_event_with_empty_event_id() {
let event = AutoFunctionStreamEvent::new(
AutoFunctionStreamChunk::Delta(StepDelta::Text {
text: "Test".to_string(),
}),
Some(String::new()),
);
let json = serde_json::to_string(&event).expect("Serialization should succeed");
assert!(
json.contains(r#""event_id":"""#),
"Should have empty event_id"
);
let deserialized: AutoFunctionStreamEvent =
serde_json::from_str(&json).expect("Deserialization should succeed");
assert_eq!(deserialized.event_id.as_deref(), Some(""));
}
#[test]
fn test_pending_function_call() {
let call = PendingFunctionCall::new("get_weather", "call-123", json!({"city": "Seattle"}));
assert_eq!(call.name, "get_weather");
assert_eq!(call.call_id, "call-123");
assert_eq!(call.args, json!({"city": "Seattle"}));
}
#[test]
fn test_pending_function_call_serialization_roundtrip() {
let call = PendingFunctionCall::new("test_func", "id-456", json!({"key": "value"}));
let json_str = serde_json::to_string(&call).expect("Serialization should succeed");
assert!(json_str.contains("test_func"));
assert!(json_str.contains("id-456"));
let deserialized: PendingFunctionCall =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
assert_eq!(deserialized, call);
}
#[test]
fn test_executing_functions_new_format_roundtrip() {
use crate::InteractionStatus;
let chunk = AutoFunctionStreamChunk::ExecutingFunctions {
response: crate::InteractionResponse {
id: Some("interaction-new".to_string()),
model: Some("gemini-3-flash-preview".to_string()),
status: InteractionStatus::Completed,
..Default::default()
},
pending_calls: vec![
PendingFunctionCall::new("func1", "call-1", json!({"a": 1})),
PendingFunctionCall::new("func2", "call-2", json!({"b": 2})),
],
};
let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
assert!(json_str.contains("pending_calls"));
assert!(json_str.contains("func1"));
assert!(json_str.contains("func2"));
let deserialized: AutoFunctionStreamChunk =
serde_json::from_str(&json_str).expect("Deserialization should succeed");
match deserialized {
AutoFunctionStreamChunk::ExecutingFunctions {
response,
pending_calls,
} => {
assert_eq!(response.id.as_deref(), Some("interaction-new"));
assert_eq!(pending_calls.len(), 2);
assert_eq!(pending_calls[0].name, "func1");
assert_eq!(pending_calls[1].name, "func2");
}
_ => panic!("Expected ExecutingFunctions variant"),
}
}
}