#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
strum::IntoStaticStr,
strum::EnumString,
strum::EnumIter,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum Operation {
ListModels,
GetModel,
CountTokens,
GenerateContent,
StreamGenerateContent,
CreateModeration,
CreateDecision,
GuardianReview,
GuardianClassify,
CompactContent,
SummarizeMemory,
CreateConversation,
CreateEmbedding,
BatchCreateEmbedding,
Rerank,
WebSearch,
CreateImage,
EditImage,
CreateSpeech,
CreateTranscription,
CreateTranslation,
CreateFile,
ListFiles,
RetrieveFile,
RetrieveFileContent,
DeleteFile,
CreateVideo,
RetrieveVideo,
ListVideos,
DeleteVideo,
DownloadVideoContent,
CreateRealtimeCall,
ConnectRealtime,
}
impl Operation {
pub const fn produces_usage(self) -> bool {
match self {
Self::GenerateContent
| Self::StreamGenerateContent
| Self::CreateModeration
| Self::CreateDecision
| Self::GuardianReview
| Self::GuardianClassify
| Self::CompactContent
| Self::SummarizeMemory
| Self::CreateEmbedding
| Self::BatchCreateEmbedding
| Self::Rerank
| Self::WebSearch
| Self::CreateImage
| Self::EditImage
| Self::CreateSpeech
| Self::CreateTranscription
| Self::CreateTranslation
| Self::CreateVideo
| Self::ConnectRealtime => true,
Self::ListModels
| Self::GetModel
| Self::CountTokens
| Self::CreateConversation
| Self::CreateFile
| Self::ListFiles
| Self::RetrieveFile
| Self::RetrieveFileContent
| Self::DeleteFile
| Self::RetrieveVideo
| Self::ListVideos
| Self::DeleteVideo
| Self::DownloadVideoContent
| Self::CreateRealtimeCall => false,
}
}
pub fn usage_operations() -> impl Iterator<Item = Self> {
<Self as strum::IntoEnumIterator>::iter().filter(|op| op.produces_usage())
}
pub fn id(self) -> &'static str {
self.into()
}
pub fn from_id(value: &str) -> Option<Self> {
value.parse().ok()
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
strum::IntoStaticStr,
strum::EnumString,
strum::EnumIter,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum WireFamily {
#[serde(rename = "openai")]
#[strum(serialize = "openai")]
OpenAi,
Claude,
Gemini,
}
impl WireFamily {
pub fn id(self) -> &'static str {
self.into()
}
pub fn from_id(value: &str) -> Option<Self> {
value.parse().ok()
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
strum::IntoStaticStr,
strum::EnumString,
strum::EnumIter,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
pub enum Dialect {
#[serde(rename = "openai")]
#[strum(serialize = "openai")]
OpenAi,
Claude,
Gemini,
#[serde(rename = "openai_chat")]
#[strum(serialize = "openai_chat")]
OpenAiChat,
#[serde(rename = "openai_responses_websocket")]
#[strum(serialize = "openai_responses_websocket")]
OpenAiResponsesWebSocket,
}
impl Dialect {
pub fn id(self) -> &'static str {
self.into()
}
pub fn from_id(value: &str) -> Option<Self> {
value.parse().ok()
}
pub const fn family(self) -> WireFamily {
match self {
Self::OpenAi | Self::OpenAiChat | Self::OpenAiResponsesWebSocket => WireFamily::OpenAi,
Self::Claude => WireFamily::Claude,
Self::Gemini => WireFamily::Gemini,
}
}
pub const fn pair_dialect(self) -> Self {
match self {
Self::OpenAiResponsesWebSocket => Self::OpenAi,
other => other,
}
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
#[serde(deny_unknown_fields)]
pub struct OperationKey {
pub operation: Operation,
pub dialect: Dialect,
}
impl OperationKey {
pub const fn family(self) -> WireFamily {
self.dialect.family()
}
pub const fn pair_key(self) -> Self {
Self {
operation: self.operation,
dialect: self.dialect.pair_dialect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn only_inference_and_metered_tool_operations_produce_usage() {
for op in [
Operation::ListModels,
Operation::GetModel,
Operation::CountTokens,
Operation::CreateConversation,
Operation::CreateFile,
Operation::ListFiles,
Operation::RetrieveFile,
Operation::RetrieveFileContent,
Operation::DeleteFile,
Operation::RetrieveVideo,
Operation::ListVideos,
Operation::DeleteVideo,
Operation::DownloadVideoContent,
Operation::CreateRealtimeCall,
] {
assert!(!op.produces_usage(), "{}", op.id());
}
assert!(Operation::GenerateContent.produces_usage());
assert!(Operation::CreateEmbedding.produces_usage());
assert!(Operation::ConnectRealtime.produces_usage());
}
#[test]
fn operation_ids_are_pinned() {
let ids: Vec<&'static str> = Operation::iter().map(Operation::id).collect();
assert_eq!(
ids,
[
"list_models",
"get_model",
"count_tokens",
"generate_content",
"stream_generate_content",
"create_moderation",
"create_decision",
"guardian_review",
"guardian_classify",
"compact_content",
"summarize_memory",
"create_conversation",
"create_embedding",
"batch_create_embedding",
"rerank",
"web_search",
"create_image",
"edit_image",
"create_speech",
"create_transcription",
"create_translation",
"create_file",
"list_files",
"retrieve_file",
"retrieve_file_content",
"delete_file",
"create_video",
"retrieve_video",
"list_videos",
"delete_video",
"download_video_content",
"create_realtime_call",
"connect_realtime",
]
);
}
#[test]
fn dialect_ids_are_pinned() {
let ids: Vec<&'static str> = Dialect::iter().map(Dialect::id).collect();
assert_eq!(
ids,
[
"openai",
"claude",
"gemini",
"openai_chat",
"openai_responses_websocket",
]
);
let families: Vec<&'static str> = WireFamily::iter().map(WireFamily::id).collect();
assert_eq!(families, ["openai", "claude", "gemini"]);
}
#[test]
fn every_id_round_trips() {
for operation in Operation::iter() {
assert_eq!(Operation::from_id(operation.id()), Some(operation));
}
for dialect in Dialect::iter() {
assert_eq!(Dialect::from_id(dialect.id()), Some(dialect));
}
for family in WireFamily::iter() {
assert_eq!(WireFamily::from_id(family.id()), Some(family));
}
}
#[test]
fn serde_agrees_with_strum() {
for operation in Operation::iter() {
let json = serde_json::to_string(&operation).expect("serializes");
assert_eq!(json, format!("\"{}\"", operation.id()));
let back: Operation = serde_json::from_str(&json).expect("deserializes");
assert_eq!(back, operation);
}
for dialect in Dialect::iter() {
let json = serde_json::to_string(&dialect).expect("serializes");
assert_eq!(json, format!("\"{}\"", dialect.id()));
let back: Dialect = serde_json::from_str(&json).expect("deserializes");
assert_eq!(back, dialect);
}
for family in WireFamily::iter() {
let json = serde_json::to_string(&family).expect("serializes");
assert_eq!(json, format!("\"{}\"", family.id()));
}
}
#[test]
fn unknown_ids_are_rejected() {
assert_eq!(Operation::from_id("not_an_operation"), None);
assert_eq!(Dialect::from_id("open_ai"), None);
assert_eq!(WireFamily::from_id("open_ai"), None);
}
#[test]
fn families_are_derivable() {
assert_eq!(Dialect::OpenAiChat.family(), WireFamily::OpenAi);
assert_eq!(
Dialect::OpenAiResponsesWebSocket.family(),
WireFamily::OpenAi
);
assert_eq!(Dialect::OpenAi.family(), WireFamily::OpenAi);
assert_eq!(Dialect::Claude.family(), WireFamily::Claude);
assert_eq!(Dialect::Gemini.family(), WireFamily::Gemini);
}
#[test]
fn only_the_websocket_envelope_borrows_another_dialects_pairs() {
let ws = OperationKey {
operation: Operation::StreamGenerateContent,
dialect: Dialect::OpenAiResponsesWebSocket,
};
assert_eq!(
ws.pair_key(),
OperationKey {
operation: Operation::StreamGenerateContent,
dialect: Dialect::OpenAi,
}
);
for dialect in Dialect::iter().filter(|d| *d != Dialect::OpenAiResponsesWebSocket) {
assert_eq!(dialect.pair_dialect(), dialect);
}
}
}