gproxy_protocol/transform/models/
list.rs1use super::{
2 common, get,
3 supplement::{
4 ClaudeModelSupplement, Converted, GeminiModelSupplement, ListPageFacts,
5 OpenAiModelSupplement, missing_pagination,
6 },
7};
8use crate::transform::{Report, TransformError};
9use crate::wire::{
10 claude::models as claude_models, gemini::models as gemini_models,
11 openai::models as openai_models,
12};
13use std::collections::BTreeMap;
14
15fn supplement_for<'a, T>(
18 supplements: &'a BTreeMap<String, T>,
19 source_key: &str,
20) -> Result<&'a T, TransformError> {
21 supplements
22 .get(source_key)
23 .ok_or_else(|| TransformError::missing_metadata(format!("model[{source_key}].supplement")))
24}
25
26pub fn claude_to_openai_list(
27 input: claude_models::ListModelsResponseBody,
28 supplements: &BTreeMap<String, OpenAiModelSupplement>,
29 _page: &ListPageFacts,
30) -> Result<Converted<openai_models::ListModelsResponseBody>, TransformError> {
31 let mut report = Report::default();
32 let mut data = Vec::with_capacity(input.data.len());
33 for model in input.data {
34 let supplement = supplement_for(supplements, &model.id)?;
35 let converted = get::claude_to_openai(model, supplement)?;
36 common::merge_report(&mut report, converted.report);
37 data.push(converted.value);
38 }
39
40 report.omitted("pagination", "OpenAI model lists have no cursor envelope");
41 Ok(common::converted(
42 openai_models::ListModelsResponseBody {
43 data,
44 object: openai_models::ListObject::List,
45 rest: Default::default(),
46 },
47 report,
48 ))
49}
50
51pub fn claude_to_gemini_list(
52 input: claude_models::ListModelsResponseBody,
53 supplements: &BTreeMap<String, GeminiModelSupplement>,
54 page: &ListPageFacts,
55) -> Result<Converted<gemini_models::ListModelsResponseBody>, TransformError> {
56 let mut report = Report::default();
57 let mut models = Vec::with_capacity(input.data.len());
58 for model in input.data {
59 let supplement = supplement_for(supplements, &model.id)?;
60 let converted = get::claude_to_gemini(model, supplement)?;
61 common::merge_report(&mut report, converted.report);
62 models.push(converted.value);
63 }
64
65 Ok(common::converted(
66 gemini_models::ListModelsResponseBody {
67 models: Some(models),
68 next_page_token: page.next_page_token.clone(),
69 rest: Default::default(),
70 },
71 report,
72 ))
73}
74
75pub fn openai_to_claude_list(
76 input: openai_models::ListModelsResponseBody,
77 supplements: &BTreeMap<String, ClaudeModelSupplement>,
78 page: &ListPageFacts,
79) -> Result<Converted<claude_models::ListModelsResponseBody>, TransformError> {
80 let mut report = Report::default();
81 let mut data = Vec::with_capacity(input.data.len());
82 for model in input.data {
83 let supplement = supplement_for(supplements, &model.id)?;
84 let converted = get::openai_to_claude(model, supplement)?;
85 common::merge_report(&mut report, converted.report);
86 data.push(converted.value);
87 }
88
89 let (first_id, last_id, has_more) = claude_page(&data, page)?;
90 Ok(common::converted(
91 claude_models::ListModelsResponseBody {
92 first_id,
93 last_id,
94 has_more,
95 data,
96 rest: Default::default(),
97 },
98 report,
99 ))
100}
101
102pub fn openai_to_gemini_list(
103 input: openai_models::ListModelsResponseBody,
104 supplements: &BTreeMap<String, GeminiModelSupplement>,
105 page: &ListPageFacts,
106) -> Result<Converted<gemini_models::ListModelsResponseBody>, TransformError> {
107 let mut report = Report::default();
108 let mut models = Vec::with_capacity(input.data.len());
109 for model in input.data {
110 let supplement = supplement_for(supplements, &model.id)?;
111 let converted = get::openai_to_gemini(model, supplement)?;
112 common::merge_report(&mut report, converted.report);
113 models.push(converted.value);
114 }
115
116 Ok(common::converted(
117 gemini_models::ListModelsResponseBody {
118 models: Some(models),
119 next_page_token: page.next_page_token.clone(),
120 rest: Default::default(),
121 },
122 report,
123 ))
124}
125
126pub fn gemini_to_openai_list(
127 input: gemini_models::ListModelsResponseBody,
128 supplements: &BTreeMap<String, OpenAiModelSupplement>,
129 _page: &ListPageFacts,
130) -> Result<Converted<openai_models::ListModelsResponseBody>, TransformError> {
131 let mut report = Report::default();
132 let source_models = input.models.unwrap_or_default();
133 let mut data = Vec::with_capacity(source_models.len());
134 for model in source_models {
135 let supplement = supplement_for(supplements, &model.name)?;
136 let converted = get::gemini_to_openai(model, supplement)?;
137 common::merge_report(&mut report, converted.report);
138 data.push(converted.value);
139 }
140
141 report.omitted("pagination", "OpenAI model lists have no cursor envelope");
142 Ok(common::converted(
143 openai_models::ListModelsResponseBody {
144 data,
145 object: openai_models::ListObject::List,
146 rest: Default::default(),
147 },
148 report,
149 ))
150}
151
152pub fn gemini_to_claude_list(
153 input: gemini_models::ListModelsResponseBody,
154 supplements: &BTreeMap<String, ClaudeModelSupplement>,
155 page: &ListPageFacts,
156) -> Result<Converted<claude_models::ListModelsResponseBody>, TransformError> {
157 let mut report = Report::default();
158 let source_models = input.models.unwrap_or_default();
159 let mut data = Vec::with_capacity(source_models.len());
160 for model in source_models {
161 let supplement = supplement_for(supplements, &model.name)?;
162 let converted = get::gemini_to_claude(model, supplement)?;
163 common::merge_report(&mut report, converted.report);
164 data.push(converted.value);
165 }
166
167 let (first_id, last_id, has_more) = claude_page(&data, page)?;
168 Ok(common::converted(
169 claude_models::ListModelsResponseBody {
170 first_id,
171 last_id,
172 has_more,
173 data,
174 rest: Default::default(),
175 },
176 report,
177 ))
178}
179
180fn claude_page(
181 models: &[claude_models::ModelInfo],
182 facts: &ListPageFacts,
183) -> Result<(String, String, bool), TransformError> {
184 let derived_first = models.first().map(|model| model.id.clone());
185 let derived_last = models.last().map(|model| model.id.clone());
186 let first_id = match (&facts.first_id, derived_first) {
187 (Some(supplied), Some(derived)) if supplied != &derived => {
188 return Err(TransformError::new(
189 crate::transform::TransformErrorKind::Conflict,
190 "pagination.first_id",
191 "page fact disagrees with converted list",
192 ));
193 }
194 (Some(supplied), _) => supplied.clone(),
195 (None, Some(derived)) if facts.complete => derived,
196 _ => {
197 return Err(missing_pagination(
198 "Claude first_id requires complete list or explicit page facts (NeedsPagination)",
199 ));
200 }
201 };
202 let last_id = match (&facts.last_id, derived_last) {
203 (Some(supplied), Some(derived)) if supplied != &derived => {
204 return Err(TransformError::new(
205 crate::transform::TransformErrorKind::Conflict,
206 "pagination.last_id",
207 "page fact disagrees with converted list",
208 ));
209 }
210 (Some(supplied), _) => supplied.clone(),
211 (None, Some(derived)) if facts.complete => derived,
212 _ => {
213 return Err(missing_pagination(
214 "Claude last_id requires complete list or explicit page facts (NeedsPagination)",
215 ));
216 }
217 };
218 let has_more = match (facts.has_more, facts.complete) {
219 (Some(value), _) => value,
220 (None, true) => false,
221 (None, false) => {
222 return Err(missing_pagination(
223 "Claude has_more requires explicit page facts (NeedsPagination)",
224 ));
225 }
226 };
227 Ok((first_id, last_id, has_more))
228}