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}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct ClaudeCapabilities {
31    pub batch: bool,
32    pub citations: bool,
33    pub code_execution: bool,
34    pub context_management: ClaudeContextManagement,
35    pub effort: ClaudeEffort,
36    pub image_input: bool,
37    pub pdf_input: bool,
38    pub structured_outputs: bool,
39    #[serde(default)]
40    pub server_tools: Option<ClaudeServerTools>,
41    pub thinking: ClaudeThinking,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct ClaudeContextManagement {
46    pub clear_thinking_20251015: bool,
47    pub clear_tool_uses_20250919: bool,
48    pub compact_20260112: bool,
49    pub supported: bool,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct ClaudeEffort {
54    pub high: bool,
55    pub low: bool,
56    pub max: bool,
57    pub medium: bool,
58    pub supported: bool,
59    pub xhigh: bool,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct ClaudeThinking {
64    pub supported: bool,
65    pub adaptive: bool,
66    pub enabled: bool,
67    #[serde(default)]
68    pub disabled: Option<bool>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ClaudeServerTools {
73    pub supported: bool,
74    pub web_search: bool,
75    pub code_execution: bool,
76}
77
78impl ClaudeCapabilities {
79    pub(crate) fn into_wire(self) -> claude_models::ModelCapabilities {
80        let support = |supported| claude_models::CapabilitySupport {
81            supported,
82            rest: Default::default(),
83        };
84        claude_models::ModelCapabilities {
85            batch: support(self.batch),
86            citations: support(self.citations),
87            code_execution: support(self.code_execution),
88            context_management: claude_models::ContextManagementCapability {
89                clear_thinking_20251015: support(self.context_management.clear_thinking_20251015),
90                clear_tool_uses_20250919: support(self.context_management.clear_tool_uses_20250919),
91                compact_20260112: support(self.context_management.compact_20260112),
92                supported: self.context_management.supported,
93                rest: Default::default(),
94            },
95            effort: claude_models::EffortCapability {
96                high: support(self.effort.high),
97                low: support(self.effort.low),
98                max: support(self.effort.max),
99                medium: support(self.effort.medium),
100                supported: self.effort.supported,
101                xhigh: support(self.effort.xhigh),
102                rest: Default::default(),
103            },
104            image_input: support(self.image_input),
105            pdf_input: support(self.pdf_input),
106            structured_outputs: support(self.structured_outputs),
107            server_tools: self
108                .server_tools
109                .map(|tools| claude_models::ServerToolsCapability {
110                    supported: tools.supported,
111                    web_search: support(tools.web_search),
112                    code_execution: support(tools.code_execution),
113                    rest: Default::default(),
114                }),
115            thinking: claude_models::ThinkingCapability {
116                supported: self.thinking.supported,
117                types: claude_models::ThinkingTypes {
118                    adaptive: support(self.thinking.adaptive),
119                    enabled: support(self.thinking.enabled),
120                    disabled: self.thinking.disabled.map(support),
121                    rest: Default::default(),
122                },
123                rest: Default::default(),
124            },
125            rest: Default::default(),
126        }
127    }
128}
129
130/// Facts for Gemini's required `baseModelId` and `version` fields.
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct GeminiModelSupplement {
133    pub base_model_id: String,
134    pub version: String,
135}
136
137/// Pagination facts are target-specific. A cursor is never renamed to a page
138/// token implicitly. `complete` means the caller has aggregated the complete
139/// directory from the first request through its terminal page; it does not
140/// mean merely that the current source page has `has_more = false`.
141#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
142pub struct ListPageFacts {
143    pub complete: bool,
144    pub first_id: Option<String>,
145    pub last_id: Option<String>,
146    pub has_more: Option<bool>,
147    pub next_page_token: Option<String>,
148}
149
150impl ListPageFacts {
151    pub const fn complete() -> Self {
152        Self {
153            complete: true,
154            first_id: None,
155            last_id: None,
156            has_more: Some(false),
157            next_page_token: None,
158        }
159    }
160
161    pub fn explicit(
162        first_id: impl Into<String>,
163        last_id: impl Into<String>,
164        has_more: bool,
165    ) -> Self {
166        Self {
167            complete: false,
168            first_id: Some(first_id.into()),
169            last_id: Some(last_id.into()),
170            has_more: Some(has_more),
171            next_page_token: None,
172        }
173    }
174
175    pub fn with_next_page_token(mut self, token: impl Into<String>) -> Self {
176        self.next_page_token = Some(token.into());
177        self
178    }
179}
180
181pub(crate) fn missing_pagination(detail: &str) -> TransformError {
182    TransformError::new(
183        crate::transform::TransformErrorKind::MissingMetadata,
184        "pagination",
185        detail,
186    )
187}