use super::{
common, get,
supplement::{
ClaudeModelSupplement, Converted, GeminiModelSupplement, ListPageFacts,
OpenAiModelSupplement, missing_pagination,
},
};
use crate::transform::{Report, TransformError};
use crate::wire::{
claude::models as claude_models, gemini::models as gemini_models,
openai::models as openai_models,
};
use std::collections::BTreeMap;
fn supplement_for<'a, T>(
supplements: &'a BTreeMap<String, T>,
source_key: &str,
) -> Result<&'a T, TransformError> {
supplements
.get(source_key)
.ok_or_else(|| TransformError::missing_metadata(format!("model[{source_key}].supplement")))
}
pub fn claude_to_openai_list(
input: claude_models::ListModelsResponseBody,
supplements: &BTreeMap<String, OpenAiModelSupplement>,
_page: &ListPageFacts,
) -> Result<Converted<openai_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let mut data = Vec::with_capacity(input.data.len());
for model in input.data {
let supplement = supplement_for(supplements, &model.id)?;
let converted = get::claude_to_openai(model, supplement)?;
common::merge_report(&mut report, converted.report);
data.push(converted.value);
}
report.omitted("pagination", "OpenAI model lists have no cursor envelope");
Ok(common::converted(
openai_models::ListModelsResponseBody {
data,
object: openai_models::ListObject::List,
rest: Default::default(),
},
report,
))
}
pub fn claude_to_gemini_list(
input: claude_models::ListModelsResponseBody,
supplements: &BTreeMap<String, GeminiModelSupplement>,
page: &ListPageFacts,
) -> Result<Converted<gemini_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let mut models = Vec::with_capacity(input.data.len());
for model in input.data {
let supplement = supplement_for(supplements, &model.id)?;
let converted = get::claude_to_gemini(model, supplement)?;
common::merge_report(&mut report, converted.report);
models.push(converted.value);
}
Ok(common::converted(
gemini_models::ListModelsResponseBody {
models: Some(models),
next_page_token: page.next_page_token.clone(),
rest: Default::default(),
},
report,
))
}
pub fn openai_to_claude_list(
input: openai_models::ListModelsResponseBody,
supplements: &BTreeMap<String, ClaudeModelSupplement>,
page: &ListPageFacts,
) -> Result<Converted<claude_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let mut data = Vec::with_capacity(input.data.len());
for model in input.data {
let supplement = supplement_for(supplements, &model.id)?;
let converted = get::openai_to_claude(model, supplement)?;
common::merge_report(&mut report, converted.report);
data.push(converted.value);
}
let (first_id, last_id, has_more) = claude_page(&data, page)?;
Ok(common::converted(
claude_models::ListModelsResponseBody {
first_id,
last_id,
has_more,
data,
rest: Default::default(),
},
report,
))
}
pub fn openai_to_gemini_list(
input: openai_models::ListModelsResponseBody,
supplements: &BTreeMap<String, GeminiModelSupplement>,
page: &ListPageFacts,
) -> Result<Converted<gemini_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let mut models = Vec::with_capacity(input.data.len());
for model in input.data {
let supplement = supplement_for(supplements, &model.id)?;
let converted = get::openai_to_gemini(model, supplement)?;
common::merge_report(&mut report, converted.report);
models.push(converted.value);
}
Ok(common::converted(
gemini_models::ListModelsResponseBody {
models: Some(models),
next_page_token: page.next_page_token.clone(),
rest: Default::default(),
},
report,
))
}
pub fn gemini_to_openai_list(
input: gemini_models::ListModelsResponseBody,
supplements: &BTreeMap<String, OpenAiModelSupplement>,
_page: &ListPageFacts,
) -> Result<Converted<openai_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let source_models = input.models.unwrap_or_default();
let mut data = Vec::with_capacity(source_models.len());
for model in source_models {
let supplement = supplement_for(supplements, &model.name)?;
let converted = get::gemini_to_openai(model, supplement)?;
common::merge_report(&mut report, converted.report);
data.push(converted.value);
}
report.omitted("pagination", "OpenAI model lists have no cursor envelope");
Ok(common::converted(
openai_models::ListModelsResponseBody {
data,
object: openai_models::ListObject::List,
rest: Default::default(),
},
report,
))
}
pub fn gemini_to_claude_list(
input: gemini_models::ListModelsResponseBody,
supplements: &BTreeMap<String, ClaudeModelSupplement>,
page: &ListPageFacts,
) -> Result<Converted<claude_models::ListModelsResponseBody>, TransformError> {
let mut report = Report::default();
let source_models = input.models.unwrap_or_default();
let mut data = Vec::with_capacity(source_models.len());
for model in source_models {
let supplement = supplement_for(supplements, &model.name)?;
let converted = get::gemini_to_claude(model, supplement)?;
common::merge_report(&mut report, converted.report);
data.push(converted.value);
}
let (first_id, last_id, has_more) = claude_page(&data, page)?;
Ok(common::converted(
claude_models::ListModelsResponseBody {
first_id,
last_id,
has_more,
data,
rest: Default::default(),
},
report,
))
}
fn claude_page(
models: &[claude_models::ModelInfo],
facts: &ListPageFacts,
) -> Result<(String, String, bool), TransformError> {
let derived_first = models.first().map(|model| model.id.clone());
let derived_last = models.last().map(|model| model.id.clone());
let first_id = match (&facts.first_id, derived_first) {
(Some(supplied), Some(derived)) if supplied != &derived => {
return Err(TransformError::new(
crate::transform::TransformErrorKind::Conflict,
"pagination.first_id",
"page fact disagrees with converted list",
));
}
(Some(supplied), _) => supplied.clone(),
(None, Some(derived)) if facts.complete => derived,
_ => {
return Err(missing_pagination(
"Claude first_id requires complete list or explicit page facts (NeedsPagination)",
));
}
};
let last_id = match (&facts.last_id, derived_last) {
(Some(supplied), Some(derived)) if supplied != &derived => {
return Err(TransformError::new(
crate::transform::TransformErrorKind::Conflict,
"pagination.last_id",
"page fact disagrees with converted list",
));
}
(Some(supplied), _) => supplied.clone(),
(None, Some(derived)) if facts.complete => derived,
_ => {
return Err(missing_pagination(
"Claude last_id requires complete list or explicit page facts (NeedsPagination)",
));
}
};
let has_more = match (facts.has_more, facts.complete) {
(Some(value), _) => value,
(None, true) => false,
(None, false) => {
return Err(missing_pagination(
"Claude has_more requires explicit page facts (NeedsPagination)",
));
}
};
Ok((first_id, last_id, has_more))
}