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