use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum ProtocolError {
Invalid(&'static str),
Json(serde_json::Error),
}
impl fmt::Display for ProtocolError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Invalid(message) => formatter.write_str(message),
Self::Json(error) => error.fmt(formatter),
}
}
}
impl Error for ProtocolError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Invalid(_) => None,
Self::Json(error) => Some(error),
}
}
}
impl From<serde_json::Error> for ProtocolError {
fn from(error: serde_json::Error) -> Self {
Self::Json(error)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RpcResponse<T> {
Success(T),
Error(RpcError),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RpcError {
pub code: i64,
pub message: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChatGptAccount;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReasoningEffort {
pub reasoning_effort: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Model {
pub id: String,
pub supported_reasoning_efforts: Vec<ReasoningEffort>,
pub default_reasoning_effort: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ModelPage {
pub data: Vec<Model>,
pub next_cursor: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Started {
pub id: String,
}
pub type ThreadStarted = Started;
pub type TurnStarted = Started;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MessagePhase {
Commentary,
FinalAnswer,
Unknown,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EventScope {
pub thread_id: String,
pub turn_id: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SourceCandidate {
pub url: String,
pub title: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TurnStatus {
Completed,
Failed,
Interrupted,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Usage {
pub input_tokens: Option<u64>,
pub cached_input_tokens: Option<u64>,
pub output_tokens: Option<u64>,
pub reasoning_output_tokens: Option<u64>,
pub total_tokens: Option<u64>,
}
pub type ProviderError = String;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InboundEvent {
AgentMessage {
scope: EventScope,
id: String,
phase: MessagePhase,
text: String,
},
SourceCandidates {
scope: EventScope,
id: String,
sources: Vec<SourceCandidate>,
},
UsageUpdated {
scope: EventScope,
usage: Usage,
},
TurnTerminal {
id: String,
status: TurnStatus,
usage: Option<Usage>,
error: Option<ProviderError>,
},
ProviderError(ProviderError),
Ignored {
method: String,
item_type: Option<String>,
},
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_value_traits<T: Clone + fmt::Debug + Eq + PartialEq>() {}
fn assert_scalar_traits<T: Copy>() {}
#[test]
fn values_have_expected_traits_and_shape() {
assert_value_traits::<RpcResponse<ChatGptAccount>>();
assert_value_traits::<InboundEvent>();
assert_scalar_traits::<MessagePhase>();
assert_scalar_traits::<TurnStatus>();
assert_scalar_traits::<Usage>();
assert_eq!(
RpcResponse::Success(ChatGptAccount),
RpcResponse::Success(ChatGptAccount)
);
assert!(matches!(
InboundEvent::Ignored {
method: "unknown".to_owned(),
item_type: None,
},
InboundEvent::Ignored {
method,
item_type: None,
} if method == "unknown"
));
assert!(matches!(
InboundEvent::UsageUpdated {
scope: EventScope {
thread_id: "thread".to_owned(),
turn_id: "turn".to_owned(),
},
usage: Usage {
input_tokens: Some(1),
cached_input_tokens: Some(2),
output_tokens: Some(3),
reasoning_output_tokens: Some(4),
total_tokens: Some(5),
},
},
InboundEvent::UsageUpdated {
scope: EventScope { thread_id, turn_id },
usage: Usage {
input_tokens: Some(1),
cached_input_tokens: Some(2),
output_tokens: Some(3),
reasoning_output_tokens: Some(4),
total_tokens: Some(5),
},
} if thread_id == "thread" && turn_id == "turn"
));
}
}