use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json::Value;
fn deserialize_audio_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
base64::engine::general_purpose::STANDARD.decode(&s).map_err(serde::de::Error::custom)
}
fn serialize_audio_bytes<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = base64::engine::general_purpose::STANDARD.encode(bytes);
serializer.serialize_str(&s)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ClientEvent {
#[serde(rename = "session.update")]
SessionUpdate {
session: Value,
},
#[serde(rename = "input_audio_buffer.append")]
AudioDelta {
#[serde(skip_serializing_if = "Option::is_none")]
event_id: Option<String>,
#[serde(
serialize_with = "serialize_audio_bytes",
deserialize_with = "deserialize_audio_bytes"
)]
audio: Vec<u8>,
#[serde(skip)]
format: Option<crate::audio::AudioFormat>,
},
#[serde(rename = "input_audio_buffer.commit")]
InputAudioBufferCommit,
#[serde(rename = "input_audio_buffer.clear")]
InputAudioBufferClear,
#[serde(rename = "conversation.item.create")]
ConversationItemCreate {
item: Value,
},
#[serde(rename = "response.create")]
ResponseCreate {
#[serde(skip_serializing_if = "Option::is_none")]
config: Option<Value>,
},
#[serde(rename = "response.cancel")]
ResponseCancel,
#[serde(rename = "message")]
Message {
role: String,
parts: Vec<adk_core::types::Part>,
},
#[serde(skip_serializing)]
UpdateSession {
#[serde(skip_serializing_if = "Option::is_none")]
instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
tools: Option<Vec<crate::config::ToolDefinition>>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type")]
pub item_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Vec<ContentPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub call_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentPart {
#[serde(rename = "type")]
pub content_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transcript: Option<String>,
}
impl ConversationItem {
pub fn user_text(text: impl Into<String>) -> Self {
Self {
id: None,
item_type: "message".to_string(),
role: Some("user".to_string()),
content: Some(vec![ContentPart {
content_type: "input_text".to_string(),
text: Some(text.into()),
audio: None,
transcript: None,
}]),
call_id: None,
output: None,
}
}
pub fn tool_response(call_id: impl Into<String>, output: impl Into<String>) -> Self {
Self {
id: None,
item_type: "function_call_output".to_string(),
role: None,
content: None,
call_id: Some(call_id.into()),
output: Some(output.into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ServerEvent {
#[serde(rename = "session.created")]
SessionCreated {
event_id: String,
session: Value,
},
#[serde(rename = "session.updated")]
SessionUpdated {
event_id: String,
session: Value,
},
#[serde(rename = "error")]
Error {
event_id: String,
error: ErrorInfo,
},
#[serde(rename = "input_audio_buffer.speech_started")]
SpeechStarted {
event_id: String,
audio_start_ms: u64,
},
#[serde(rename = "input_audio_buffer.speech_stopped")]
SpeechStopped {
event_id: String,
audio_end_ms: u64,
},
#[serde(rename = "input_audio_buffer.committed")]
AudioCommitted {
event_id: String,
item_id: String,
},
#[serde(rename = "input_audio_buffer.cleared")]
AudioCleared {
event_id: String,
},
#[serde(rename = "conversation.item.created")]
ItemCreated {
event_id: String,
item: Value,
},
#[serde(rename = "response.created")]
ResponseCreated {
event_id: String,
response: Value,
},
#[serde(rename = "response.done")]
ResponseDone {
event_id: String,
response: Value,
},
#[serde(rename = "response.output_item.added")]
OutputItemAdded {
event_id: String,
response_id: String,
output_index: u32,
item: Value,
},
#[serde(rename = "response.output_item.done")]
OutputItemDone {
event_id: String,
response_id: String,
output_index: u32,
item: Value,
},
#[serde(rename = "response.audio.delta")]
AudioDelta {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
#[serde(
serialize_with = "serialize_audio_bytes",
deserialize_with = "deserialize_audio_bytes"
)]
delta: Vec<u8>,
},
#[serde(rename = "response.audio.done")]
AudioDone {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
},
#[serde(rename = "response.text.delta")]
TextDelta {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
delta: String,
},
#[serde(rename = "response.text.done")]
TextDone {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
text: String,
},
#[serde(rename = "response.audio_transcript.delta")]
TranscriptDelta {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
delta: String,
},
#[serde(rename = "response.audio_transcript.done")]
TranscriptDone {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
content_index: u32,
transcript: String,
},
#[serde(rename = "response.function_call_arguments.delta")]
FunctionCallDelta {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
call_id: String,
delta: String,
},
#[serde(rename = "response.function_call_arguments.done")]
FunctionCallDone {
event_id: String,
response_id: String,
item_id: String,
output_index: u32,
call_id: String,
name: String,
arguments: String,
},
#[serde(rename = "rate_limits.updated")]
RateLimitsUpdated {
event_id: String,
rate_limits: Vec<RateLimit>,
},
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorInfo {
#[serde(rename = "type")]
pub error_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub param: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimit {
pub name: String,
pub limit: u64,
pub remaining: u64,
pub reset_seconds: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub call_id: String,
pub name: String,
pub arguments: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResponse {
pub call_id: String,
pub output: Value,
}
impl ToolResponse {
pub fn new(call_id: impl Into<String>, output: impl Serialize) -> Self {
Self {
call_id: call_id.into(),
output: serde_json::to_value(output).unwrap_or(Value::Null),
}
}
pub fn from_string(call_id: impl Into<String>, output: impl Into<String>) -> Self {
Self { call_id: call_id.into(), output: Value::String(output.into()) }
}
}