use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
macro_rules! define_sdk_enum {
(
$(#[$meta:meta])*
$name:ident {
$(
$(#[$vmeta:meta])*
$variant:ident => $wire:literal
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum $name {
$(
$(#[$vmeta])*
$variant,
)+
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
$( Self::$variant => $wire, )+
};
f.write_str(s)
}
}
impl FromStr for $name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$( $wire => Ok(Self::$variant), )+
other => Err(format!(concat!("Unrecognized ", stringify!($name), ": {:?}"), other)),
}
}
}
};
}
macro_rules! define_sdk_enum_custom_serde {
(
$(#[$meta:meta])*
$name:ident {
$(
$(#[$vmeta:meta])*
$variant:ident => $wire:literal
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum $name {
$(
$(#[$vmeta])*
#[serde(rename = $wire)]
$variant,
)+
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
$( Self::$variant => $wire, )+
};
f.write_str(s)
}
}
impl FromStr for $name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$( $wire => Ok(Self::$variant), )+
other => Err(format!(concat!("Unrecognized ", stringify!($name), ": {:?}"), other)),
}
}
}
};
}
define_sdk_enum! {
StepType {
TextResponse => "TEXT_RESPONSE",
ToolCall => "TOOL_CALL",
SystemMessage => "SYSTEM_MESSAGE",
Compaction => "COMPACTION",
Finish => "FINISH",
Thinking => "THINKING",
#[default]
Unknown => "UNKNOWN",
}
}
define_sdk_enum! {
StepSource {
System => "SYSTEM",
User => "USER",
Model => "MODEL",
#[default]
Unknown => "UNKNOWN",
}
}
define_sdk_enum! {
StepStatus {
Active => "ACTIVE",
Done => "DONE",
WaitingForUser => "WAITING_FOR_USER",
Error => "ERROR",
Canceled => "CANCELED",
#[default]
Unknown => "UNKNOWN",
}
}
define_sdk_enum_custom_serde! {
StepTarget {
Model => "TARGET_MODEL",
User => "TARGET_USER",
Environment => "TARGET_ENVIRONMENT",
Unspecified => "TARGET_UNSPECIFIED",
#[default]
Unknown => "UNKNOWN",
}
}
define_sdk_enum! {
StopReason {
Unspecified => "UNSPECIFIED",
MaxModelCallsExceeded => "MAX_MODEL_CALLS_EXCEEDED",
MaxToolCallsExceeded => "MAX_TOOL_CALLS_EXCEEDED",
MaxInputTokensExceeded => "MAX_INPUT_TOKENS_EXCEEDED",
MaxOutputTokensExceeded => "MAX_OUTPUT_TOKENS_EXCEEDED",
MaxTotalTokensExceeded => "MAX_TOTAL_TOKENS_EXCEEDED",
QuotaExhausted => "QUOTA_EXHAUSTED",
#[default]
Unknown => "UNKNOWN",
}
}
define_sdk_enum! {
Modality {
Unspecified => "MODALITY_UNSPECIFIED",
Text => "TEXT",
Image => "IMAGE",
Video => "VIDEO",
Audio => "AUDIO",
Document => "DOCUMENT",
#[default]
Unknown => "UNKNOWN",
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ModalityTokenCount {
pub modality: Modality,
pub token_count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCallInfo {
pub name: String,
#[serde(default)]
pub args: serde_json::Value,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub canonical_path: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolResult {
pub name: String,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub result: serde_json::Value,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct UsageMetadata {
#[serde(default)]
pub prompt_token_count: Option<u64>,
#[serde(default)]
pub cached_content_token_count: Option<u64>,
#[serde(default)]
pub candidates_token_count: Option<u64>,
#[serde(default)]
pub thoughts_token_count: Option<u64>,
#[serde(default)]
pub total_token_count: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub prompt_tokens_details: Vec<ModalityTokenCount>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cache_tokens_details: Vec<ModalityTokenCount>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub candidates_tokens_details: Vec<ModalityTokenCount>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_use_prompt_tokens_details: Vec<ModalityTokenCount>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum MessageRole {
#[default]
User,
Model,
System,
#[serde(untagged)]
Unknown(String),
}
impl std::fmt::Display for MessageRole {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::User => f.write_str("user"),
Self::Model => f.write_str("model"),
Self::System => f.write_str("system"),
Self::Unknown(s) => f.write_str(s),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversationMessage {
#[serde(default)]
pub role: MessageRole,
#[serde(default)]
pub content: String,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, TypedBuilder)]
#[builder(field_defaults(default))]
pub struct Step {
#[serde(default)]
#[builder(setter(into))]
pub id: String,
#[serde(default)]
pub step_index: u32,
#[serde(default)]
#[builder(setter(into))]
pub trajectory_id: String,
#[serde(default)]
#[builder(setter(into))]
pub cascade_id: String,
#[serde(default)]
#[builder(setter(into))]
pub parent_trajectory_id: String,
#[serde(default)]
pub depth: u32,
#[serde(default, rename = "type")]
pub step_type: StepType,
#[serde(default)]
pub source: StepSource,
#[serde(default)]
pub target: StepTarget,
#[serde(default)]
pub status: StepStatus,
#[serde(default)]
#[builder(setter(into))]
pub content: String,
#[serde(default)]
#[builder(setter(into))]
pub content_delta: String,
#[serde(default)]
#[builder(setter(into))]
pub thinking: String,
#[serde(default)]
#[builder(setter(into))]
pub thinking_delta: String,
#[serde(default)]
#[builder(setter(transform = |v: impl IntoIterator<Item = impl Into<ToolCallInfo>>| v.into_iter().map(Into::into).collect()))]
pub tool_calls: Vec<ToolCallInfo>,
#[serde(default)]
#[builder(setter(into))]
pub error: String,
#[serde(default)]
pub http_code: u16,
#[serde(default)]
#[builder(setter(strip_option))]
pub is_complete_response: Option<bool>,
#[serde(default)]
#[builder(setter(strip_option))]
pub structured_output: Option<serde_json::Value>,
#[serde(default)]
#[builder(setter(strip_option))]
pub usage_metadata: Option<UsageMetadata>,
}
impl Step {
#[must_use]
pub fn is_subagent_step(&self) -> bool {
!self.cascade_id.is_empty()
&& !self.trajectory_id.is_empty()
&& self.trajectory_id != self.cascade_id
}
}
#[cfg(feature = "python")]
macro_rules! impl_from_py_object {
($($t:ty),+) => {
$(
impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for $t {
type Error = pyo3::PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
crate::runtime::py_scripts::warm_up_lazy_imports(ob.py());
pythonize::depythonize(&*ob).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"Failed to deserialize {} from Python dict: {}",
stringify!($t),
e
))
})
}
}
)+
};
}
#[cfg(feature = "python")]
impl_from_py_object!(
StepType,
StepSource,
StepStatus,
StepTarget,
StopReason,
Modality,
ModalityTokenCount,
ToolCallInfo,
ToolResult,
UsageMetadata,
MessageRole,
ConversationMessage,
Step
);
#[cfg(test)]
#[cfg(test)]
#[path = "types_tests.rs"]
mod tests;