use crate::anthropic::{MessageCreateRequest, MessageResponse};
use crate::config::TranslationConfig;
use crate::error::TranslateError;
use crate::gemini::request::GenerateContentRequest;
use crate::gemini::response::GenerateContentResponse;
pub use crate::mapping::warnings::TranslationWarnings;
use crate::mapping::{
gemini_message_map, gemini_streaming_map, message_map, responses_message_map,
responses_streaming_map, reverse_message_map, reverse_streaming_map, streaming_map,
};
use crate::openai::responses::{ResponsesRequest, ResponsesResponse};
use crate::openai::{ChatCompletionRequest, ChatCompletionResponse};
pub fn compute_request_warnings(req: &MessageCreateRequest) -> TranslationWarnings {
message_map::compute_request_warnings(req)
}
pub fn translate_request(
req: &MessageCreateRequest,
config: &TranslationConfig,
) -> Result<ChatCompletionRequest, TranslateError> {
let mut openai_req = message_map::anthropic_to_openai_request(req);
openai_req.model = config.map_model(&openai_req.model)?;
Ok(openai_req)
}
pub fn translate_response(resp: &ChatCompletionResponse, original_model: &str) -> MessageResponse {
message_map::openai_to_anthropic_response(resp, original_model)
}
pub fn new_stream_translator(model: String) -> streaming_map::StreamingTranslator {
streaming_map::StreamingTranslator::new(model)
}
pub fn translate_request_responses(
req: &MessageCreateRequest,
config: &TranslationConfig,
) -> Result<ResponsesRequest, TranslateError> {
let mut responses_req = responses_message_map::anthropic_to_responses_request(req);
responses_req.model = config.map_model(&responses_req.model)?;
Ok(responses_req)
}
pub fn translate_response_responses(
resp: &ResponsesResponse,
original_model: &str,
) -> MessageResponse {
responses_message_map::responses_to_anthropic_response(resp, original_model)
}
pub fn translate_openai_to_anthropic_request(
req: &ChatCompletionRequest,
warnings: &mut TranslationWarnings,
) -> Result<MessageCreateRequest, TranslateError> {
reverse_message_map::openai_to_anthropic_request(req, warnings)
}
pub fn translate_openai_to_anthropic_request_with_context(
req: &ChatCompletionRequest,
warnings: &mut TranslationWarnings,
) -> Result<
(
MessageCreateRequest,
reverse_message_map::AnthropicTranslationContext,
),
TranslateError,
> {
reverse_message_map::openai_to_anthropic_request_with_context(req, warnings)
}
pub fn translate_anthropic_to_openai_response(
resp: &MessageResponse,
model: &str,
) -> ChatCompletionResponse {
reverse_message_map::anthropic_to_openai_response(resp, model)
}
pub fn translate_anthropic_to_openai_response_with_context(
resp: &MessageResponse,
model: &str,
context: &reverse_message_map::AnthropicTranslationContext,
) -> ChatCompletionResponse {
reverse_message_map::anthropic_to_openai_response_with_context(resp, model, context)
}
pub fn new_reverse_stream_translator(
id: String,
model: String,
) -> reverse_streaming_map::ReverseStreamingTranslator {
reverse_streaming_map::ReverseStreamingTranslator::new(id, model)
}
pub fn new_responses_stream_translator(
model: String,
) -> responses_streaming_map::ResponsesStreamingTranslator {
responses_streaming_map::ResponsesStreamingTranslator::new(model)
}
pub fn translate_request_gemini(
req: &MessageCreateRequest,
config: &TranslationConfig,
) -> Result<(GenerateContentRequest, String), TranslateError> {
let gemini_req = gemini_message_map::anthropic_to_gemini_request(req);
let model = config.map_model(&req.model)?;
Ok((gemini_req, model))
}
pub fn translate_response_gemini(resp: &GenerateContentResponse, model: &str) -> MessageResponse {
gemini_message_map::gemini_to_anthropic_response(resp, model)
}
pub fn new_gemini_stream_translator(
model: String,
) -> gemini_streaming_map::GeminiStreamingTranslator {
gemini_streaming_map::GeminiStreamingTranslator::new(model)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::LossyBehavior;
fn sample_request() -> MessageCreateRequest {
serde_json::from_str(
r#"{
"model": "claude-sonnet-4-6",
"max_tokens": 100,
"messages": [{"role": "user", "content": "Hello"}]
}"#,
)
.unwrap()
}
#[test]
fn translate_request_with_default_config() {
let config = TranslationConfig::default();
let req = sample_request();
let openai_req = translate_request(&req, &config).unwrap();
assert_eq!(openai_req.model, "claude-sonnet-4-6");
assert_eq!(openai_req.max_completion_tokens, Some(100));
}
#[test]
fn translate_request_with_model_mapping() {
let config = TranslationConfig::builder()
.model_map("haiku", "gpt-4o-mini")
.model_map("sonnet", "gpt-4o")
.build();
let req = sample_request();
let openai_req = translate_request(&req, &config).unwrap();
assert_eq!(openai_req.model, "gpt-4o");
}
#[test]
fn translate_request_unknown_model_passthrough() {
let config = TranslationConfig::builder()
.model_map("sonnet", "gpt-4o")
.build();
let req: MessageCreateRequest = serde_json::from_str(
r#"{
"model": "custom-model",
"max_tokens": 50,
"messages": [{"role": "user", "content": "Hi"}]
}"#,
)
.unwrap();
let openai_req = translate_request(&req, &config).unwrap();
assert_eq!(openai_req.model, "custom-model");
}
#[test]
fn translate_request_unknown_model_strict() {
let config = TranslationConfig::builder()
.model_map("sonnet", "gpt-4o")
.passthrough_unknown_models(false)
.build();
let req: MessageCreateRequest = serde_json::from_str(
r#"{
"model": "custom-model",
"max_tokens": 50,
"messages": [{"role": "user", "content": "Hi"}]
}"#,
)
.unwrap();
let err = translate_request(&req, &config).unwrap_err();
assert!(matches!(err, TranslateError::UnknownModel(_)));
}
#[test]
fn translate_response_roundtrip() {
let openai_resp: ChatCompletionResponse = serde_json::from_str(
r#"{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1700000000,
"model": "gpt-4o",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Hi there!"},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}
}"#,
)
.unwrap();
let anthropic_resp = translate_response(&openai_resp, "claude-sonnet-4-6");
assert_eq!(anthropic_resp.model, "claude-sonnet-4-6");
assert_eq!(anthropic_resp.usage.input_tokens, 10);
assert_eq!(anthropic_resp.usage.output_tokens, 5);
}
#[test]
fn builder_ergonomics() {
let config = TranslationConfig::builder()
.model_map("haiku", "gemini-2.5-flash")
.model_map("sonnet", "gemini-2.5-pro")
.model_map("opus", "gemini-2.5-pro")
.lossy_behavior(LossyBehavior::Silent)
.passthrough_unknown_models(false)
.build();
assert_eq!(config.model_map.len(), 3);
assert_eq!(config.lossy_behavior, LossyBehavior::Silent);
assert!(!config.passthrough_unknown_models);
}
}