Skip to main content

gproxy_protocol/transform/models/
supplement.rs

1pub use crate::transform::Converted;
2use crate::transform::TransformError;
3use crate::wire::claude::models as claude_models;
4use serde::{Deserialize, Serialize};
5
6/// Facts needed by targets whose model schema requires an owner or creation
7/// timestamp absent from the source. A source fact always wins; a conflicting
8/// supplied fact is rejected.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct OpenAiModelSupplement {
11    pub owned_by: String,
12    pub created: Option<i64>,
13}
14
15/// Target-specific Claude metadata. The nested capability facts are rebuilt
16/// into fresh wire structs so no supplement or source `rest` map is copied.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct ClaudeModelSupplement {
19    pub display_name: Option<String>,
20    #[serde(default)]
21    pub line: Option<String>,
22    pub allowed_fallback_models: Vec<String>,
23    pub capabilities: ClaudeCapabilities,
24    pub max_input_tokens: Option<i64>,
25    pub max_tokens: Option<i64>,
26    pub created_at: Option<String>,
27    #[serde(default)]
28    pub lifecycle: Option<claude_models::ModelLifecycle>,
29    #[serde(default)]
30    pub deprecated_at: Option<String>,
31    #[serde(default)]
32    pub retires_at: Option<String>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct ClaudeCapabilities {
37    pub batch: bool,
38    pub citations: bool,
39    pub code_execution: bool,
40    pub context_management: ClaudeContextManagement,
41    pub effort: ClaudeEffort,
42    pub image_input: bool,
43    pub pdf_input: bool,
44    pub structured_outputs: bool,
45    #[serde(default)]
46    pub server_tools: Option<ClaudeServerTools>,
47    #[serde(default)]
48    pub compaction: Option<ClaudeCompaction>,
49    pub thinking: ClaudeThinking,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct ClaudeContextManagement {
54    pub clear_thinking_20251015: bool,
55    pub clear_tool_uses_20250919: bool,
56    pub compact_20260112: bool,
57    pub supported: bool,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct ClaudeEffort {
62    pub high: bool,
63    pub low: bool,
64    pub max: bool,
65    pub medium: bool,
66    pub supported: bool,
67    pub xhigh: bool,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct ClaudeThinking {
72    pub supported: bool,
73    pub adaptive: bool,
74    pub enabled: bool,
75    #[serde(default)]
76    pub disabled: Option<bool>,
77    #[serde(default)]
78    pub between_tools: Option<bool>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct ClaudeServerTools {
83    pub supported: bool,
84    pub web_search: bool,
85    pub code_execution: bool,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct ClaudeCompaction {
90    pub supported: bool,
91    pub summarize: bool,
92}
93
94impl ClaudeCapabilities {
95    pub(crate) fn into_wire(self) -> claude_models::ModelCapabilities {
96        let support = |supported| claude_models::CapabilitySupport {
97            supported,
98            rest: Default::default(),
99        };
100        claude_models::ModelCapabilities {
101            batch: support(self.batch),
102            citations: support(self.citations),
103            code_execution: support(self.code_execution),
104            compaction: self.compaction.map(|value| {
105                Some(claude_models::CompactionCapability {
106                    supported: value.supported,
107                    summarize: support(value.summarize),
108                    rest: Default::default(),
109                })
110            }),
111            context_management: claude_models::ContextManagementCapability {
112                clear_thinking_20251015: Some(support(
113                    self.context_management.clear_thinking_20251015,
114                )),
115                clear_tool_uses_20250919: Some(support(
116                    self.context_management.clear_tool_uses_20250919,
117                )),
118                compact_20260112: Some(support(self.context_management.compact_20260112)),
119                supported: self.context_management.supported,
120                rest: Default::default(),
121            },
122            effort: claude_models::EffortCapability {
123                high: support(self.effort.high),
124                low: support(self.effort.low),
125                max: support(self.effort.max),
126                medium: support(self.effort.medium),
127                supported: self.effort.supported,
128                xhigh: Some(support(self.effort.xhigh)),
129                rest: Default::default(),
130            },
131            image_input: support(self.image_input),
132            pdf_input: support(self.pdf_input),
133            structured_outputs: support(self.structured_outputs),
134            server_tools: self
135                .server_tools
136                .map(|tools| claude_models::ServerToolsCapability {
137                    supported: tools.supported,
138                    web_search: support(tools.web_search),
139                    code_execution: support(tools.code_execution),
140                    rest: Default::default(),
141                }),
142            thinking: claude_models::ThinkingCapability {
143                supported: self.thinking.supported,
144                types: claude_models::ThinkingTypes {
145                    adaptive: support(self.thinking.adaptive),
146                    enabled: support(self.thinking.enabled),
147                    disabled: self.thinking.disabled.map(support),
148                    between_tools: self.thinking.between_tools.map(support),
149                    rest: Default::default(),
150                },
151                rest: Default::default(),
152            },
153            rest: Default::default(),
154        }
155    }
156}
157
158/// Facts for Gemini's required `baseModelId` and `version` fields.
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160pub struct GeminiModelSupplement {
161    pub base_model_id: String,
162    pub version: String,
163}
164
165/// Pagination facts are target-specific. A cursor is never renamed to a page
166/// token implicitly. `complete` means the caller has aggregated the complete
167/// directory from the first request through its terminal page; it does not
168/// mean merely that the current source page has `has_more = false`.
169#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
170pub struct ListPageFacts {
171    pub complete: bool,
172    pub first_id: Option<String>,
173    pub last_id: Option<String>,
174    pub has_more: Option<bool>,
175    pub next_page_token: Option<String>,
176}
177
178impl ListPageFacts {
179    pub const fn complete() -> Self {
180        Self {
181            complete: true,
182            first_id: None,
183            last_id: None,
184            has_more: Some(false),
185            next_page_token: None,
186        }
187    }
188
189    pub fn explicit(
190        first_id: impl Into<String>,
191        last_id: impl Into<String>,
192        has_more: bool,
193    ) -> Self {
194        Self {
195            complete: false,
196            first_id: Some(first_id.into()),
197            last_id: Some(last_id.into()),
198            has_more: Some(has_more),
199            next_page_token: None,
200        }
201    }
202
203    pub fn with_next_page_token(mut self, token: impl Into<String>) -> Self {
204        self.next_page_token = Some(token.into());
205        self
206    }
207}
208
209pub(crate) fn missing_pagination(detail: &str) -> TransformError {
210    TransformError::new(
211        crate::transform::TransformErrorKind::MissingMetadata,
212        "pagination",
213        detail,
214    )
215}