2025-09-10 07:40:30 -07:00
use crate ::apis ::anthropic ::MessagesRequest ;
2025-10-14 14:01:11 -07:00
use crate ::apis ::openai ::ChatCompletionsRequest ;
2025-10-22 11:31:21 -07:00
use crate ::apis ::amazon_bedrock ::{ ConverseRequest , ConverseStreamRequest } ;
2025-12-03 14:58:26 -08:00
use crate ::apis ::openai_responses ::ResponsesAPIRequest ;
use crate ::clients ::endpoints ::SupportedAPIsFromClient ;
2025-10-22 11:31:21 -07:00
use crate ::clients ::endpoints ::SupportedUpstreamAPIs ;
2025-09-10 07:40:30 -07:00
use serde_json ::Value ;
2025-10-14 14:01:11 -07:00
use std ::collections ::HashMap ;
2025-08-20 12:55:29 -07:00
use std ::error ::Error ;
use std ::fmt ;
2025-12-03 14:58:26 -08:00
#[ derive(Clone, Debug) ]
2025-08-20 12:55:29 -07:00
pub enum ProviderRequestType {
ChatCompletionsRequest ( ChatCompletionsRequest ) ,
2025-09-10 07:40:30 -07:00
MessagesRequest ( MessagesRequest ) ,
2025-10-22 11:31:21 -07:00
BedrockConverse ( ConverseRequest ) ,
BedrockConverseStream ( ConverseStreamRequest ) ,
2025-12-03 14:58:26 -08:00
ResponsesAPIRequest ( ResponsesAPIRequest ) ,
2025-08-20 12:55:29 -07:00
//add more request types here
}
pub trait ProviderRequest : Send + Sync {
/// Extract the model name from the request
fn model ( & self ) -> & str ;
/// Set the model name for the request
fn set_model ( & mut self , model : String ) ;
/// Check if this is a streaming request
fn is_streaming ( & self ) -> bool ;
/// Extract text content from messages for token counting
fn extract_messages_text ( & self ) -> String ;
/// Extract the user message for tracing/logging purposes
fn get_recent_user_message ( & self ) -> Option < String > ;
2025-12-11 15:21:57 -08:00
/// Get tool names if tools are defined in the request
fn get_tool_names ( & self ) -> Option < Vec < String > > ;
2025-08-20 12:55:29 -07:00
/// Convert the request to bytes for transmission
fn to_bytes ( & self ) -> Result < Vec < u8 > , ProviderRequestError > ;
2025-09-10 07:40:30 -07:00
fn metadata ( & self ) -> & Option < HashMap < String , Value > > ;
/// Remove a metadata key from the request and return true if the key was present
fn remove_metadata_key ( & mut self , key : & str ) -> bool ;
2025-12-11 15:21:57 -08:00
fn get_temperature ( & self ) -> Option < f32 > ;
2025-08-20 12:55:29 -07:00
}
impl ProviderRequest for ProviderRequestType {
fn model ( & self ) -> & str {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . model ( ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . model ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . model ( ) ,
Self ::BedrockConverseStream ( r ) = > r . model ( ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . model ( ) ,
2025-08-20 12:55:29 -07:00
}
}
fn set_model ( & mut self , model : String ) {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . set_model ( model ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . set_model ( model ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . set_model ( model ) ,
Self ::BedrockConverseStream ( r ) = > r . set_model ( model ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . set_model ( model ) ,
2025-08-20 12:55:29 -07:00
}
}
fn is_streaming ( & self ) -> bool {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . is_streaming ( ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . is_streaming ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( _ ) = > false ,
Self ::BedrockConverseStream ( _ ) = > true ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . is_streaming ( ) ,
2025-08-20 12:55:29 -07:00
}
}
fn extract_messages_text ( & self ) -> String {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . extract_messages_text ( ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . extract_messages_text ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . extract_messages_text ( ) ,
Self ::BedrockConverseStream ( r ) = > r . extract_messages_text ( ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . extract_messages_text ( ) ,
2025-08-20 12:55:29 -07:00
}
}
fn get_recent_user_message ( & self ) -> Option < String > {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . get_recent_user_message ( ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . get_recent_user_message ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . get_recent_user_message ( ) ,
Self ::BedrockConverseStream ( r ) = > r . get_recent_user_message ( ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . get_recent_user_message ( ) ,
2025-08-20 12:55:29 -07:00
}
}
2025-12-11 15:21:57 -08:00
fn get_tool_names ( & self ) -> Option < Vec < String > > {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . get_tool_names ( ) ,
Self ::MessagesRequest ( r ) = > r . get_tool_names ( ) ,
Self ::BedrockConverse ( r ) = > r . get_tool_names ( ) ,
Self ::BedrockConverseStream ( r ) = > r . get_tool_names ( ) ,
Self ::ResponsesAPIRequest ( r ) = > r . get_tool_names ( ) ,
}
}
2025-08-20 12:55:29 -07:00
fn to_bytes ( & self ) -> Result < Vec < u8 > , ProviderRequestError > {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . to_bytes ( ) ,
2025-09-10 07:40:30 -07:00
Self ::MessagesRequest ( r ) = > r . to_bytes ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . to_bytes ( ) ,
Self ::BedrockConverseStream ( r ) = > r . to_bytes ( ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . to_bytes ( ) ,
2025-09-10 07:40:30 -07:00
}
}
fn metadata ( & self ) -> & Option < HashMap < String , Value > > {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . metadata ( ) ,
Self ::MessagesRequest ( r ) = > r . metadata ( ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . metadata ( ) ,
Self ::BedrockConverseStream ( r ) = > r . metadata ( ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . metadata ( ) ,
2025-09-10 07:40:30 -07:00
}
}
fn remove_metadata_key ( & mut self , key : & str ) -> bool {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . remove_metadata_key ( key ) ,
Self ::MessagesRequest ( r ) = > r . remove_metadata_key ( key ) ,
2025-10-22 11:31:21 -07:00
Self ::BedrockConverse ( r ) = > r . remove_metadata_key ( key ) ,
Self ::BedrockConverseStream ( r ) = > r . remove_metadata_key ( key ) ,
2025-12-03 14:58:26 -08:00
Self ::ResponsesAPIRequest ( r ) = > r . remove_metadata_key ( key ) ,
2025-09-10 07:40:30 -07:00
}
}
2025-12-11 15:21:57 -08:00
fn get_temperature ( & self ) -> Option < f32 > {
match self {
Self ::ChatCompletionsRequest ( r ) = > r . get_temperature ( ) ,
Self ::MessagesRequest ( r ) = > r . get_temperature ( ) ,
Self ::BedrockConverse ( r ) = > r . get_temperature ( ) ,
Self ::BedrockConverseStream ( r ) = > r . get_temperature ( ) ,
Self ::ResponsesAPIRequest ( r ) = > r . get_temperature ( ) ,
}
}
2025-09-10 07:40:30 -07:00
}
/// Parse the client API from a byte slice.
2025-12-03 14:58:26 -08:00
impl TryFrom < ( & [ u8 ] , & SupportedAPIsFromClient ) > for ProviderRequestType {
2025-09-10 07:40:30 -07:00
type Error = std ::io ::Error ;
2025-12-03 14:58:26 -08:00
fn try_from ( ( bytes , client_api ) : ( & [ u8 ] , & SupportedAPIsFromClient ) ) -> Result < Self , Self ::Error > {
2025-09-10 07:40:30 -07:00
// Use SupportedApi to determine the appropriate request type
match client_api {
2025-12-03 14:58:26 -08:00
SupportedAPIsFromClient ::OpenAIChatCompletions ( _ ) = > {
2025-10-14 14:01:11 -07:00
let chat_completion_request : ChatCompletionsRequest =
ChatCompletionsRequest ::try_from ( bytes )
2025-09-10 07:40:30 -07:00
. map_err ( | e | std ::io ::Error ::new ( std ::io ::ErrorKind ::InvalidData , e ) ) ? ;
2025-10-14 14:01:11 -07:00
Ok ( ProviderRequestType ::ChatCompletionsRequest (
chat_completion_request ,
) )
}
2025-12-03 14:58:26 -08:00
SupportedAPIsFromClient ::AnthropicMessagesAPI ( _ ) = > {
2025-10-14 14:01:11 -07:00
let messages_request : MessagesRequest = MessagesRequest ::try_from ( bytes )
. map_err ( | e | std ::io ::Error ::new ( std ::io ::ErrorKind ::InvalidData , e ) ) ? ;
Ok ( ProviderRequestType ::MessagesRequest ( messages_request ) )
}
2025-12-03 14:58:26 -08:00
SupportedAPIsFromClient ::OpenAIResponsesAPI ( _ ) = > {
let responses_apirequest : ResponsesAPIRequest =
ResponsesAPIRequest ::try_from ( bytes )
. map_err ( | e | std ::io ::Error ::new ( std ::io ::ErrorKind ::InvalidData , e ) ) ? ;
Ok ( ProviderRequestType ::ResponsesAPIRequest (
responses_apirequest ,
) )
}
2025-08-20 12:55:29 -07:00
}
}
}
2025-09-10 07:40:30 -07:00
/// Conversion from one ProviderRequestType to a different ProviderRequestType (SupportedAPIs)
2025-10-22 11:31:21 -07:00
impl TryFrom < ( ProviderRequestType , & SupportedUpstreamAPIs ) > for ProviderRequestType {
2025-09-10 07:40:30 -07:00
type Error = ProviderRequestError ;
2025-10-14 14:01:11 -07:00
fn try_from (
2025-10-22 11:31:21 -07:00
( client_request , upstream_api ) : ( ProviderRequestType , & SupportedUpstreamAPIs ) ,
2025-10-14 14:01:11 -07:00
) -> Result < Self , Self ::Error > {
2025-10-22 11:31:21 -07:00
match ( client_request , upstream_api ) {
2025-12-03 14:58:26 -08:00
// ============================================================================
// ChatCompletionsRequest conversions
// ============================================================================
2025-10-14 14:01:11 -07:00
(
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ,
2025-10-22 11:31:21 -07:00
SupportedUpstreamAPIs ::OpenAIChatCompletions ( _ ) ,
2025-10-14 14:01:11 -07:00
) = > Ok ( ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ) ,
(
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ,
2025-10-22 11:31:21 -07:00
SupportedUpstreamAPIs ::AnthropicMessagesAPI ( _ ) ,
2025-10-14 14:01:11 -07:00
) = > {
let messages_req =
MessagesRequest ::try_from ( chat_req ) . map_err ( | e | ProviderRequestError {
message : format ! (
" Failed to convert ChatCompletionsRequest to MessagesRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
2025-09-10 07:40:30 -07:00
} ) ? ;
Ok ( ProviderRequestType ::MessagesRequest ( messages_req ) )
}
2025-10-22 11:31:21 -07:00
(
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverse ( _ ) ,
) = > {
let bedrock_req = ConverseRequest ::try_from ( chat_req )
. map_err ( | e | ProviderRequestError {
message : format ! ( " Failed to convert ChatCompletionsRequest to Amazon Bedrock request: {} " , e ) ,
source : Some ( Box ::new ( e ) )
} ) ? ;
Ok ( ProviderRequestType ::BedrockConverse ( bedrock_req ) )
}
(
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverseStream ( _ ) ,
) = > {
let bedrock_req = ConverseStreamRequest ::try_from ( chat_req )
. map_err ( | e | ProviderRequestError {
2025-12-03 14:58:26 -08:00
message : format ! ( " Failed to convert ChatCompletionsRequest to Amazon Bedrock Stream request: {} " , e ) ,
2025-10-22 11:31:21 -07:00
source : Some ( Box ::new ( e ) )
} ) ? ;
2025-12-03 14:58:26 -08:00
Ok ( ProviderRequestType ::BedrockConverseStream ( bedrock_req ) )
}
(
ProviderRequestType ::ChatCompletionsRequest ( _ ) ,
SupportedUpstreamAPIs ::OpenAIResponsesAPI ( _ ) ,
) = > {
Err ( ProviderRequestError {
message : " Conversion from ChatCompletionsRequest to ResponsesAPIRequest is not supported. ResponsesAPI can only be used as a client API, not as an upstream API. " . to_string ( ) ,
source : None ,
} )
}
// ============================================================================
// MessagesRequest conversions
// ============================================================================
(
ProviderRequestType ::MessagesRequest ( messages_req ) ,
SupportedUpstreamAPIs ::AnthropicMessagesAPI ( _ ) ,
) = > Ok ( ProviderRequestType ::MessagesRequest ( messages_req ) ) ,
(
ProviderRequestType ::MessagesRequest ( messages_req ) ,
SupportedUpstreamAPIs ::OpenAIChatCompletions ( _ ) ,
) = > {
let chat_req = ChatCompletionsRequest ::try_from ( messages_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert MessagesRequest to ChatCompletionsRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::ChatCompletionsRequest ( chat_req ) )
2025-10-22 11:31:21 -07:00
}
(
ProviderRequestType ::MessagesRequest ( messages_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverse ( _ ) ,
) = > {
let bedrock_req =
ConverseRequest ::try_from ( messages_req ) . map_err ( | e | ProviderRequestError {
message : format ! (
" Failed to convert MessagesRequest to Amazon Bedrock request: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
} ) ? ;
Ok ( ProviderRequestType ::BedrockConverse ( bedrock_req ) )
}
(
ProviderRequestType ::MessagesRequest ( messages_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverseStream ( _ ) ,
) = > {
let bedrock_req = ConverseStreamRequest ::try_from ( messages_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
2025-12-03 14:58:26 -08:00
" Failed to convert MessagesRequest to Amazon Bedrock Stream request: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::BedrockConverseStream ( bedrock_req ) )
}
(
ProviderRequestType ::MessagesRequest ( _ ) ,
SupportedUpstreamAPIs ::OpenAIResponsesAPI ( _ ) ,
) = > {
Err ( ProviderRequestError {
message : " Conversion from MessagesRequest to ResponsesAPIRequest is not supported. ResponsesAPI can only be used as a client API, not as an upstream API. " . to_string ( ) ,
source : None ,
} )
}
// ============================================================================
// ResponsesAPIRequest conversions (only converts TO other formats)
// ============================================================================
(
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
SupportedUpstreamAPIs ::OpenAIResponsesAPI ( _ ) ,
) = > Ok ( ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ) ,
// ResponsesAPI -> ChatCompletions (direct conversion)
(
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
SupportedUpstreamAPIs ::OpenAIChatCompletions ( _ ) ,
) = > {
let chat_req = ChatCompletionsRequest ::try_from ( responses_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ResponsesAPIRequest to ChatCompletionsRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::ChatCompletionsRequest ( chat_req ) )
}
// ResponsesAPI -> Anthropic Messages (via ChatCompletions)
(
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
SupportedUpstreamAPIs ::AnthropicMessagesAPI ( _ ) ,
) = > {
// Chain: ResponsesAPI -> ChatCompletions -> MessagesRequest
let chat_req = ChatCompletionsRequest ::try_from ( responses_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ResponsesAPIRequest to ChatCompletionsRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
let messages_req = MessagesRequest ::try_from ( chat_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ChatCompletionsRequest to MessagesRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::MessagesRequest ( messages_req ) )
}
// ResponsesAPI -> Bedrock Converse (via ChatCompletions)
(
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverse ( _ ) ,
) = > {
// Chain: ResponsesAPI -> ChatCompletions -> ConverseRequest
let chat_req = ChatCompletionsRequest ::try_from ( responses_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ResponsesAPIRequest to ChatCompletionsRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
let bedrock_req = ConverseRequest ::try_from ( chat_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ChatCompletionsRequest to Amazon Bedrock request: {} " ,
2025-10-22 11:31:21 -07:00
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::BedrockConverse ( bedrock_req ) )
}
2025-12-03 14:58:26 -08:00
// ResponsesAPI -> Bedrock Converse Stream (via ChatCompletions)
(
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
SupportedUpstreamAPIs ::AmazonBedrockConverseStream ( _ ) ,
) = > {
// Chain: ResponsesAPI -> ChatCompletions -> ConverseStreamRequest
let chat_req = ChatCompletionsRequest ::try_from ( responses_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ResponsesAPIRequest to ChatCompletionsRequest: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
let bedrock_req = ConverseStreamRequest ::try_from ( chat_req ) . map_err ( | e | {
ProviderRequestError {
message : format ! (
" Failed to convert ChatCompletionsRequest to Amazon Bedrock Stream request: {} " ,
e
) ,
source : Some ( Box ::new ( e ) ) ,
}
} ) ? ;
Ok ( ProviderRequestType ::BedrockConverseStream ( bedrock_req ) )
}
// ============================================================================
// Amazon Bedrock conversions (not supported as client API)
// ============================================================================
2025-10-22 11:31:21 -07:00
( ProviderRequestType ::BedrockConverse ( _ ) , _ ) = > {
2025-12-03 14:58:26 -08:00
Err ( ProviderRequestError {
message : " Amazon Bedrock Converse is not supported as a client API. Only OpenAI ChatCompletions, Anthropic Messages, and OpenAI Responses APIs are supported as client APIs. " . to_string ( ) ,
source : None ,
} )
2025-10-22 11:31:21 -07:00
}
( ProviderRequestType ::BedrockConverseStream ( _ ) , _ ) = > {
2025-12-03 14:58:26 -08:00
Err ( ProviderRequestError {
message : " Amazon Bedrock Converse Stream is not supported as a client API. Only OpenAI ChatCompletions, Anthropic Messages, and OpenAI Responses APIs are supported as client APIs. " . to_string ( ) ,
source : None ,
} )
2025-10-22 11:31:21 -07:00
}
2025-09-10 07:40:30 -07:00
}
}
}
2025-08-20 12:55:29 -07:00
/// Error types for provider operations
#[ derive(Debug) ]
pub struct ProviderRequestError {
pub message : String ,
pub source : Option < Box < dyn Error + Send + Sync > > ,
}
impl fmt ::Display for ProviderRequestError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
write! ( f , " Provider request error: {} " , self . message )
}
}
impl Error for ProviderRequestError {
fn source ( & self ) -> Option < & ( dyn Error + 'static ) > {
2025-10-14 14:01:11 -07:00
self . source
. as_ref ( )
. map ( | e | e . as_ref ( ) as & ( dyn Error + 'static ) )
2025-08-20 12:55:29 -07:00
}
}
2025-09-10 07:40:30 -07:00
#[ cfg(test) ]
mod tests {
use super ::* ;
use crate ::apis ::anthropic ::AnthropicApi ::Messages ;
use crate ::apis ::anthropic ::MessagesRequest as AnthropicMessagesRequest ;
2025-10-14 14:01:11 -07:00
use crate ::apis ::openai ::ChatCompletionsRequest ;
use crate ::apis ::openai ::OpenAIApi ::ChatCompletions ;
2025-12-03 14:58:26 -08:00
use crate ::clients ::endpoints ::SupportedAPIsFromClient ;
2025-10-22 11:31:21 -07:00
use crate ::transforms ::lib ::ExtractText ;
2025-09-10 07:40:30 -07:00
use serde_json ::json ;
#[ test ]
fn test_openai_request_from_bytes ( ) {
let req = json! ( {
" model " : " gpt-4 " ,
" messages " : [
{ " role " : " system " , " content " : " You are a helpful assistant " } ,
{ " role " : " user " , " content " : " Hello! " }
]
} ) ;
let bytes = serde_json ::to_vec ( & req ) . unwrap ( ) ;
2025-12-03 14:58:26 -08:00
let api = SupportedAPIsFromClient ::OpenAIChatCompletions ( ChatCompletions ) ;
2025-09-10 07:40:30 -07:00
let result = ProviderRequestType ::try_from ( ( bytes . as_slice ( ) , & api ) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::ChatCompletionsRequest ( r ) = > {
assert_eq! ( r . model , " gpt-4 " ) ;
assert_eq! ( r . messages . len ( ) , 2 ) ;
2025-10-14 14:01:11 -07:00
}
2025-09-10 07:40:30 -07:00
_ = > panic! ( " Expected ChatCompletionsRequest variant " ) ,
}
}
#[ test ]
fn test_anthropic_request_from_bytes_with_endpoint ( ) {
let req = json! ( {
" model " : " claude-3-sonnet " ,
" system " : " You are a helpful assistant " ,
" max_tokens " : 100 ,
" messages " : [
{ " role " : " user " , " content " : " Hello! " }
]
} ) ;
let bytes = serde_json ::to_vec ( & req ) . unwrap ( ) ;
2025-12-03 14:58:26 -08:00
let endpoint = SupportedAPIsFromClient ::AnthropicMessagesAPI ( Messages ) ;
2025-09-10 07:40:30 -07:00
let result = ProviderRequestType ::try_from ( ( bytes . as_slice ( ) , & endpoint ) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::MessagesRequest ( r ) = > {
assert_eq! ( r . model , " claude-3-sonnet " ) ;
assert_eq! ( r . messages . len ( ) , 1 ) ;
2025-10-14 14:01:11 -07:00
}
2025-09-10 07:40:30 -07:00
_ = > panic! ( " Expected MessagesRequest variant " ) ,
}
}
#[ test ]
fn test_openai_request_from_bytes_with_endpoint ( ) {
let req = json! ( {
" model " : " gpt-4 " ,
" messages " : [
{ " role " : " system " , " content " : " You are a helpful assistant " } ,
{ " role " : " user " , " content " : " Hello! " }
]
} ) ;
let bytes = serde_json ::to_vec ( & req ) . unwrap ( ) ;
2025-12-03 14:58:26 -08:00
let endpoint = SupportedAPIsFromClient ::OpenAIChatCompletions ( ChatCompletions ) ;
2025-09-10 07:40:30 -07:00
let result = ProviderRequestType ::try_from ( ( bytes . as_slice ( ) , & endpoint ) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::ChatCompletionsRequest ( r ) = > {
assert_eq! ( r . model , " gpt-4 " ) ;
assert_eq! ( r . messages . len ( ) , 2 ) ;
2025-10-14 14:01:11 -07:00
}
2025-09-10 07:40:30 -07:00
_ = > panic! ( " Expected ChatCompletionsRequest variant " ) ,
}
}
#[ test ]
fn test_anthropic_request_from_bytes_wrong_endpoint ( ) {
let req = json! ( {
" model " : " claude-3-sonnet " ,
" system " : " You are a helpful assistant " ,
" messages " : [
{ " role " : " user " , " content " : " Hello! " }
]
} ) ;
let bytes = serde_json ::to_vec ( & req ) . unwrap ( ) ;
// Intentionally use OpenAI endpoint for Anthropic payload
2025-12-03 14:58:26 -08:00
let endpoint = SupportedAPIsFromClient ::OpenAIChatCompletions ( ChatCompletions ) ;
2025-09-10 07:40:30 -07:00
let result = ProviderRequestType ::try_from ( ( bytes . as_slice ( ) , & endpoint ) ) ;
// Should parse as ChatCompletionsRequest, not error
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::ChatCompletionsRequest ( r ) = > {
assert_eq! ( r . model , " claude-3-sonnet " ) ;
assert_eq! ( r . messages . len ( ) , 1 ) ;
2025-10-14 14:01:11 -07:00
}
2025-09-10 07:40:30 -07:00
_ = > panic! ( " Expected ChatCompletionsRequest variant " ) ,
}
}
#[ test ]
fn test_v1_messages_to_v1_chat_completions_roundtrip ( ) {
let anthropic_req = AnthropicMessagesRequest {
model : " claude-3-sonnet " . to_string ( ) ,
2025-10-14 14:01:11 -07:00
system : Some ( crate ::apis ::anthropic ::MessagesSystemPrompt ::Single (
" You are a helpful assistant " . to_string ( ) ,
) ) ,
messages : vec ! [ crate ::apis ::anthropic ::MessagesMessage {
role : crate ::apis ::anthropic ::MessagesRole ::User ,
content : crate ::apis ::anthropic ::MessagesMessageContent ::Single (
" Hello! " . to_string ( ) ,
) ,
} ] ,
2025-09-10 07:40:30 -07:00
max_tokens : 128 ,
container : None ,
mcp_servers : None ,
service_tier : None ,
thinking : None ,
temperature : Some ( 0.7 ) ,
top_p : Some ( 1.0 ) ,
top_k : None ,
stream : Some ( false ) ,
stop_sequences : Some ( vec! [ " \n " . to_string ( ) ] ) ,
tools : None ,
tool_choice : None ,
metadata : None ,
} ;
2025-10-14 14:01:11 -07:00
let openai_req = ChatCompletionsRequest ::try_from ( anthropic_req . clone ( ) )
. expect ( " Anthropic->OpenAI conversion failed " ) ;
let anthropic_req2 = AnthropicMessagesRequest ::try_from ( openai_req )
. expect ( " OpenAI->Anthropic conversion failed " ) ;
2025-09-10 07:40:30 -07:00
assert_eq! ( anthropic_req . model , anthropic_req2 . model ) ;
// Compare system prompt text if present
assert_eq! (
2025-10-14 14:01:11 -07:00
anthropic_req . system . as_ref ( ) . and_then ( | s | match s {
crate ::apis ::anthropic ::MessagesSystemPrompt ::Single ( t ) = > Some ( t ) ,
_ = > None ,
} ) ,
anthropic_req2 . system . as_ref ( ) . and_then ( | s | match s {
crate ::apis ::anthropic ::MessagesSystemPrompt ::Single ( t ) = > Some ( t ) ,
_ = > None ,
} )
) ;
assert_eq! (
anthropic_req . messages [ 0 ] . role ,
anthropic_req2 . messages [ 0 ] . role
2025-09-10 07:40:30 -07:00
) ;
// Compare message content text if present
assert_eq! (
anthropic_req . messages [ 0 ] . content . extract_text ( ) ,
anthropic_req2 . messages [ 0 ] . content . extract_text ( )
) ;
assert_eq! ( anthropic_req . max_tokens , anthropic_req2 . max_tokens ) ;
}
2025-10-14 14:01:11 -07:00
#[ test ]
fn test_v1_chat_completions_to_v1_messages_roundtrip ( ) {
use crate ::apis ::anthropic ::MessagesRequest as AnthropicMessagesRequest ;
use crate ::apis ::openai ::{ ChatCompletionsRequest , Message , MessageContent , Role } ;
let openai_req = ChatCompletionsRequest {
model : " gpt-4 " . to_string ( ) ,
messages : vec ! [
Message {
role : Role ::System ,
content : MessageContent ::Text ( " You are a helpful assistant " . to_string ( ) ) ,
name : None ,
tool_calls : None ,
tool_call_id : None ,
} ,
Message {
role : Role ::User ,
content : MessageContent ::Text ( " Hello! " . to_string ( ) ) ,
name : None ,
tool_calls : None ,
tool_call_id : None ,
} ,
] ,
temperature : Some ( 0.7 ) ,
top_p : Some ( 1.0 ) ,
max_tokens : Some ( 128 ) ,
stream : Some ( false ) ,
stop : Some ( vec! [ " \n " . to_string ( ) ] ) ,
tools : None ,
tool_choice : None ,
parallel_tool_calls : None ,
.. Default ::default ( )
} ;
let anthropic_req = AnthropicMessagesRequest ::try_from ( openai_req . clone ( ) )
. expect ( " OpenAI->Anthropic conversion failed " ) ;
let openai_req2 = ChatCompletionsRequest ::try_from ( anthropic_req )
. expect ( " Anthropic->OpenAI conversion failed " ) ;
assert_eq! ( openai_req . model , openai_req2 . model ) ;
assert_eq! ( openai_req . messages [ 0 ] . role , openai_req2 . messages [ 0 ] . role ) ;
assert_eq! (
openai_req . messages [ 0 ] . content . extract_text ( ) ,
openai_req2 . messages [ 0 ] . content . extract_text ( )
) ;
// After roundtrip, deprecated max_tokens should be converted to max_completion_tokens
let original_max_tokens = openai_req . max_completion_tokens . or ( openai_req . max_tokens ) ;
let roundtrip_max_tokens = openai_req2 . max_completion_tokens . or ( openai_req2 . max_tokens ) ;
assert_eq! ( original_max_tokens , roundtrip_max_tokens ) ;
}
2025-12-03 14:58:26 -08:00
#[ test ]
fn test_responses_api_request_from_bytes ( ) {
use crate ::apis ::openai ::OpenAIApi ::Responses ;
let req = json! ( {
" model " : " gpt-4o " ,
" input " : " Hello, how are you? "
} ) ;
let bytes = serde_json ::to_vec ( & req ) . unwrap ( ) ;
let api = SupportedAPIsFromClient ::OpenAIResponsesAPI ( Responses ) ;
let result = ProviderRequestType ::try_from ( ( bytes . as_slice ( ) , & api ) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::ResponsesAPIRequest ( r ) = > {
assert_eq! ( r . model , " gpt-4o " ) ;
}
_ = > panic! ( " Expected ResponsesAPIRequest variant " ) ,
}
}
#[ test ]
fn test_responses_api_to_chat_completions_conversion ( ) {
use crate ::apis ::openai ::OpenAIApi ::ChatCompletions ;
use crate ::apis ::openai_responses ::{ InputParam , ResponsesAPIRequest } ;
let responses_req = ResponsesAPIRequest {
model : " gpt-4o " . to_string ( ) ,
input : InputParam ::Text ( " Hello, world! " . to_string ( ) ) ,
temperature : Some ( 0.7 ) ,
top_p : Some ( 0.9 ) ,
max_output_tokens : Some ( 100 ) ,
stream : Some ( false ) ,
metadata : None ,
tools : None ,
tool_choice : None ,
parallel_tool_calls : None ,
instructions : None ,
modalities : None ,
user : None ,
store : None ,
reasoning_effort : None ,
include : None ,
audio : None ,
text : None ,
service_tier : None ,
top_logprobs : None ,
stream_options : None ,
truncation : None ,
conversation : None ,
previous_response_id : None ,
max_tool_calls : None ,
background : None ,
} ;
let upstream_api = SupportedUpstreamAPIs ::OpenAIChatCompletions ( ChatCompletions ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) = > {
assert_eq! ( chat_req . model , " gpt-4o " ) ;
assert_eq! ( chat_req . temperature , Some ( 0.7 ) ) ;
assert_eq! ( chat_req . top_p , Some ( 0.9 ) ) ;
assert_eq! ( chat_req . max_completion_tokens , Some ( 100 ) ) ;
assert_eq! ( chat_req . messages . len ( ) , 1 ) ;
}
_ = > panic! ( " Expected ChatCompletionsRequest variant " ) ,
}
}
#[ test ]
fn test_responses_api_to_anthropic_messages_conversion ( ) {
use crate ::apis ::anthropic ::AnthropicApi ::Messages ;
use crate ::apis ::openai_responses ::{ InputParam , ResponsesAPIRequest } ;
let responses_req = ResponsesAPIRequest {
model : " gpt-4o " . to_string ( ) ,
input : InputParam ::Text ( " Hello, Claude! " . to_string ( ) ) ,
temperature : Some ( 0.8 ) ,
max_output_tokens : Some ( 150 ) ,
stream : Some ( false ) ,
metadata : None ,
tools : None ,
tool_choice : None ,
parallel_tool_calls : None ,
instructions : Some ( " You are a helpful assistant " . to_string ( ) ) ,
modalities : None ,
user : None ,
store : None ,
reasoning_effort : None ,
include : None ,
audio : None ,
text : None ,
service_tier : None ,
top_p : None ,
top_logprobs : None ,
stream_options : None ,
truncation : None ,
conversation : None ,
previous_response_id : None ,
max_tool_calls : None ,
background : None ,
} ;
let upstream_api = SupportedUpstreamAPIs ::AnthropicMessagesAPI ( Messages ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::MessagesRequest ( messages_req ) = > {
assert_eq! ( messages_req . model , " gpt-4o " ) ;
assert_eq! ( messages_req . temperature , Some ( 0.8 ) ) ;
assert_eq! ( messages_req . max_tokens , 150 ) ;
// Instructions should be converted to system prompt via ChatCompletions conversion
// The conversion chain: ResponsesAPI -> ChatCompletions (system message) -> Anthropic (system prompt)
// But we need to check if the system prompt was actually set
assert_eq! ( messages_req . messages . len ( ) , 1 ) ;
}
_ = > panic! ( " Expected MessagesRequest variant " ) ,
}
}
#[ test ]
fn test_responses_api_to_bedrock_conversion ( ) {
use crate ::apis ::amazon_bedrock ::AmazonBedrockApi ::Converse ;
use crate ::apis ::openai_responses ::{ InputParam , ResponsesAPIRequest } ;
let responses_req = ResponsesAPIRequest {
model : " gpt-4o " . to_string ( ) ,
input : InputParam ::Text ( " Hello, Bedrock! " . to_string ( ) ) ,
temperature : Some ( 0.5 ) ,
max_output_tokens : Some ( 200 ) ,
stream : Some ( false ) ,
metadata : None ,
tools : None ,
tool_choice : None ,
parallel_tool_calls : None ,
instructions : None ,
modalities : None ,
user : None ,
store : None ,
reasoning_effort : None ,
include : None ,
audio : None ,
text : None ,
service_tier : None ,
top_p : None ,
top_logprobs : None ,
stream_options : None ,
truncation : None ,
conversation : None ,
previous_response_id : None ,
max_tool_calls : None ,
background : None ,
} ;
let upstream_api = SupportedUpstreamAPIs ::AmazonBedrockConverse ( Converse ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::ResponsesAPIRequest ( responses_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_ok ( ) ) ;
match result . unwrap ( ) {
ProviderRequestType ::BedrockConverse ( bedrock_req ) = > {
assert_eq! ( bedrock_req . model_id , " gpt-4o " ) ;
// Bedrock receives the converted request through ChatCompletions
assert! ( ! bedrock_req . messages . is_none ( ) ) ;
}
_ = > panic! ( " Expected BedrockConverse variant " ) ,
}
}
#[ test ]
fn test_chat_completions_to_responses_api_not_supported ( ) {
use crate ::apis ::openai ::OpenAIApi ::Responses ;
use crate ::apis ::openai ::{ Message , MessageContent , Role } ;
let chat_req = ChatCompletionsRequest {
model : " gpt-4 " . to_string ( ) ,
messages : vec ! [ Message {
role : Role ::User ,
content : MessageContent ::Text ( " Hello! " . to_string ( ) ) ,
name : None ,
tool_calls : None ,
tool_call_id : None ,
} ] ,
.. Default ::default ( )
} ;
let upstream_api = SupportedUpstreamAPIs ::OpenAIResponsesAPI ( Responses ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::ChatCompletionsRequest ( chat_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_err ( ) ) ;
let err = result . unwrap_err ( ) ;
assert! ( err . message . contains ( " ResponsesAPI can only be used as a client API " ) ) ;
}
#[ test ]
fn test_anthropic_messages_to_responses_api_not_supported ( ) {
use crate ::apis ::anthropic ::MessagesRequest as AnthropicMessagesRequest ;
use crate ::apis ::openai ::OpenAIApi ::Responses ;
let messages_req = AnthropicMessagesRequest {
model : " claude-3-sonnet " . to_string ( ) ,
messages : vec ! [ crate ::apis ::anthropic ::MessagesMessage {
role : crate ::apis ::anthropic ::MessagesRole ::User ,
content : crate ::apis ::anthropic ::MessagesMessageContent ::Single (
" Hello! " . to_string ( ) ,
) ,
} ] ,
max_tokens : 100 ,
container : None ,
mcp_servers : None ,
service_tier : None ,
thinking : None ,
temperature : None ,
top_p : None ,
top_k : None ,
stream : None ,
stop_sequences : None ,
system : None ,
tools : None ,
tool_choice : None ,
metadata : None ,
} ;
let upstream_api = SupportedUpstreamAPIs ::OpenAIResponsesAPI ( Responses ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::MessagesRequest ( messages_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_err ( ) ) ;
let err = result . unwrap_err ( ) ;
assert! ( err . message . contains ( " ResponsesAPI can only be used as a client API " ) ) ;
}
#[ test ]
fn test_bedrock_as_client_api_not_supported ( ) {
use crate ::apis ::openai ::OpenAIApi ::ChatCompletions ;
// Create a simple Bedrock request (we'll use Default if available, or minimal construction)
let bedrock_req = ConverseRequest ::default ( ) ;
let upstream_api = SupportedUpstreamAPIs ::OpenAIChatCompletions ( ChatCompletions ) ;
let result = ProviderRequestType ::try_from ( (
ProviderRequestType ::BedrockConverse ( bedrock_req ) ,
& upstream_api ,
) ) ;
assert! ( result . is_err ( ) ) ;
let err = result . unwrap_err ( ) ;
assert! ( err . message . contains ( " not supported as a client API " ) ) ;
assert! ( err
. message
. contains ( " OpenAI ChatCompletions, Anthropic Messages, and OpenAI Responses " ) ) ;
}
2025-09-10 07:40:30 -07:00
}