Skip to main content

gproxy_protocol/protocol/
operation.rs

1//! Shared operation taxonomy and endpoint metadata.
2
3use serde::{Deserialize, Serialize};
4
5/// Upstream protocol family.
6///
7/// Provider-specific wire modules (`openai`, `claude`, `gemini`) should reuse
8/// this enum when declaring endpoint metadata or routing rules.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Provider {
12    OpenAi,
13    Claude,
14    Gemini,
15}
16
17/// Coarse operation family, used to organize protocol support by capability.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum OperationGroup {
21    Models,
22    CountTokens,
23    GenerateContent,
24    Images,
25    Embeddings,
26    Compact,
27    Conversation,
28    Realtime,
29}
30
31/// Provider-neutral operation name.
32///
33/// Variants are capability-oriented. A provider module should model only the
34/// variants that the provider actually exposes; unsupported operations are not
35/// represented by synthetic request/response types.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum Operation {
39    ListModels,
40    GetModel,
41    CountTokens,
42    GenerateContent,
43    StreamGenerateContent,
44    CreateImage,
45    EditImage,
46    CreateEmbedding,
47    CompactContent,
48    CreateConversation,
49    ConnectRealtime,
50}
51
52impl Operation {
53    /// Return the operation group for this operation.
54    pub const fn group(self) -> OperationGroup {
55        match self {
56            Self::ListModels | Self::GetModel => OperationGroup::Models,
57            Self::CountTokens => OperationGroup::CountTokens,
58            Self::GenerateContent | Self::StreamGenerateContent => OperationGroup::GenerateContent,
59            Self::CreateImage | Self::EditImage => OperationGroup::Images,
60            Self::CreateEmbedding => OperationGroup::Embeddings,
61            Self::CompactContent => OperationGroup::Compact,
62            Self::CreateConversation => OperationGroup::Conversation,
63            Self::ConnectRealtime => OperationGroup::Realtime,
64        }
65    }
66
67    /// Whether requests of this operation carry a JSON body.
68    pub const fn has_request_body(self) -> bool {
69        !matches!(
70            self,
71            Self::ListModels | Self::GetModel | Self::ConnectRealtime
72        )
73    }
74}
75
76/// Wire-format kind used together with [`Operation`].
77///
78/// Content generation needs a four-way kind because OpenAI has two distinct
79/// native formats for the same capability. Non-content operations only need the
80/// three provider families.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82#[serde(untagged)]
83pub enum OperationKind {
84    ContentGeneration(ContentGenerationKind),
85    Provider(Provider),
86}
87
88impl OperationKind {
89    pub const fn provider(self) -> Provider {
90        match self {
91            Self::ContentGeneration(kind) => kind.provider(),
92            Self::Provider(provider) => provider,
93        }
94    }
95
96    pub const fn is_content_generation(self) -> bool {
97        matches!(self, Self::ContentGeneration(_))
98    }
99}
100
101/// Content-generation wire formats.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum ContentGenerationKind {
105    OpenAiResponses,
106    #[serde(rename = "open_ai_responses_websocket")]
107    OpenAiResponsesWebSocket,
108    OpenAiChatCompletions,
109    ClaudeMessages,
110    GeminiGenerateContent,
111}
112
113impl ContentGenerationKind {
114    pub const fn provider(self) -> Provider {
115        match self {
116            Self::OpenAiResponses
117            | Self::OpenAiResponsesWebSocket
118            | Self::OpenAiChatCompletions => Provider::OpenAi,
119            Self::ClaudeMessages => Provider::Claude,
120            Self::GeminiGenerateContent => Provider::Gemini,
121        }
122    }
123}
124
125/// Capability plus wire-format kind.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
127pub struct OperationKey {
128    pub operation: Operation,
129    pub kind: OperationKind,
130}
131
132impl OperationKey {
133    pub fn content_generation(operation: Operation, kind: ContentGenerationKind) -> Self {
134        debug_assert!(
135            operation.is_content_generation(),
136            "content-generation kind used with non-content operation"
137        );
138        Self {
139            operation,
140            kind: OperationKind::ContentGeneration(kind),
141        }
142    }
143
144    pub fn provider(operation: Operation, provider: Provider) -> Self {
145        debug_assert!(
146            !operation.is_content_generation(),
147            "provider kind used with content-generation operation"
148        );
149        Self {
150            operation,
151            kind: OperationKind::Provider(provider),
152        }
153    }
154
155    pub const fn group(self) -> OperationGroup {
156        self.operation.group()
157    }
158
159    pub const fn provider_family(self) -> Provider {
160        self.kind.provider()
161    }
162
163    pub const fn is_consistent(self) -> bool {
164        self.operation.is_content_generation() == self.kind.is_content_generation()
165    }
166}
167
168impl Operation {
169    pub const fn is_content_generation(self) -> bool {
170        matches!(self, Self::GenerateContent | Self::StreamGenerateContent)
171    }
172}
173
174/// HTTP method for an upstream endpoint.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
176#[serde(rename_all = "UPPERCASE")]
177pub enum HttpMethod {
178    Get,
179    Post,
180    Put,
181    Patch,
182    Delete,
183}
184
185impl From<HttpMethod> for http::Method {
186    fn from(m: HttpMethod) -> Self {
187        match m {
188            HttpMethod::Get => http::Method::GET,
189            HttpMethod::Post => http::Method::POST,
190            HttpMethod::Put => http::Method::PUT,
191            HttpMethod::Patch => http::Method::PATCH,
192            HttpMethod::Delete => http::Method::DELETE,
193        }
194    }
195}
196
197/// Provider endpoint metadata used by routing and protocol modules.
198#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
199pub struct Endpoint {
200    pub operation_key: OperationKey,
201    pub method: HttpMethod,
202    /// Provider-relative path template, e.g. `/v1/chat/completions`.
203    pub path: String,
204}
205
206impl Endpoint {
207    pub fn new(operation_key: OperationKey, method: HttpMethod, path: impl Into<String>) -> Self {
208        Self {
209            operation_key,
210            method,
211            path: path.into(),
212        }
213    }
214
215    pub fn content_generation(
216        operation: Operation,
217        kind: ContentGenerationKind,
218        method: HttpMethod,
219        path: impl Into<String>,
220    ) -> Self {
221        Self::new(
222            OperationKey::content_generation(operation, kind),
223            method,
224            path,
225        )
226    }
227
228    pub fn provider(
229        operation: Operation,
230        provider: Provider,
231        method: HttpMethod,
232        path: impl Into<String>,
233    ) -> Self {
234        Self::new(OperationKey::provider(operation, provider), method, path)
235    }
236
237    pub const fn provider_family(&self) -> Provider {
238        self.operation_key.provider_family()
239    }
240
241    pub const fn group(&self) -> OperationGroup {
242        self.operation_key.group()
243    }
244}