mirror of
https://github.com/katanemo/plano.git
synced 2026-06-23 15:38:07 +02:00
moved away from string literals to consts
This commit is contained in:
parent
2de75d18db
commit
06c71c1392
7 changed files with 36 additions and 155 deletions
|
|
@ -4,7 +4,7 @@ use brightstaff::router::llm_router::RouterService;
|
||||||
use brightstaff::utils::tracing::init_tracer;
|
use brightstaff::utils::tracing::init_tracer;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use common::configuration::Configuration;
|
use common::configuration::Configuration;
|
||||||
use common::consts::CHAT_COMPLETIONS_PATH;
|
use common::consts::{CHAT_COMPLETIONS_PATH, MESSAGES_PATH};
|
||||||
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
use http_body_util::{combinators::BoxBody, BodyExt, Empty};
|
||||||
use hyper::body::Incoming;
|
use hyper::body::Incoming;
|
||||||
use hyper::server::conn::http1;
|
use hyper::server::conn::http1;
|
||||||
|
|
@ -112,7 +112,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
match (req.method(), req.uri().path()) {
|
match (req.method(), req.uri().path()) {
|
||||||
(&Method::POST, "/v1/chat/completions" | "/v1/messages") => {
|
(&Method::POST, CHAT_COMPLETIONS_PATH | MESSAGES_PATH) => {
|
||||||
let fully_qualified_url = format!("{}{}", llm_provider_url, req.uri().path());
|
let fully_qualified_url = format!("{}{}", llm_provider_url, req.uri().path());
|
||||||
chat(req, router_service, fully_qualified_url)
|
chat(req, router_service, fully_qualified_url)
|
||||||
.with_context(parent_cx)
|
.with_context(parent_cx)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use super::ApiDefinition;
|
||||||
use crate::providers::request::{ProviderRequest, ProviderRequestError};
|
use crate::providers::request::{ProviderRequest, ProviderRequestError};
|
||||||
use crate::providers::response::ProviderStreamResponse;
|
use crate::providers::response::ProviderStreamResponse;
|
||||||
use crate::clients::transformer::ExtractText;
|
use crate::clients::transformer::ExtractText;
|
||||||
|
use crate::{MESSAGES_PATH};
|
||||||
|
|
||||||
// Enum for all supported Anthropic APIs
|
// Enum for all supported Anthropic APIs
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
@ -21,13 +22,13 @@ pub enum AnthropicApi {
|
||||||
impl ApiDefinition for AnthropicApi {
|
impl ApiDefinition for AnthropicApi {
|
||||||
fn endpoint(&self) -> &'static str {
|
fn endpoint(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
AnthropicApi::Messages => "/v1/messages",
|
AnthropicApi::Messages => MESSAGES_PATH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_endpoint(endpoint: &str) -> Option<Self> {
|
fn from_endpoint(endpoint: &str) -> Option<Self> {
|
||||||
match endpoint {
|
match endpoint {
|
||||||
"/v1/messages" => Some(AnthropicApi::Messages),
|
MESSAGES_PATH => Some(AnthropicApi::Messages),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1044,13 +1045,13 @@ mod tests {
|
||||||
let api = AnthropicApi::Messages;
|
let api = AnthropicApi::Messages;
|
||||||
|
|
||||||
// Test trait methods
|
// Test trait methods
|
||||||
assert_eq!(api.endpoint(), "/v1/messages");
|
assert_eq!(api.endpoint(), MESSAGES_PATH);
|
||||||
assert!(api.supports_streaming());
|
assert!(api.supports_streaming());
|
||||||
assert!(api.supports_tools());
|
assert!(api.supports_tools());
|
||||||
assert!(api.supports_vision());
|
assert!(api.supports_vision());
|
||||||
|
|
||||||
// Test from_endpoint trait method
|
// Test from_endpoint trait method
|
||||||
let found_api = AnthropicApi::from_endpoint("/v1/messages");
|
let found_api = AnthropicApi::from_endpoint(MESSAGES_PATH);
|
||||||
assert_eq!(found_api, Some(AnthropicApi::Messages));
|
assert_eq!(found_api, Some(AnthropicApi::Messages));
|
||||||
|
|
||||||
let not_found = AnthropicApi::from_endpoint("/v1/unknown");
|
let not_found = AnthropicApi::from_endpoint("/v1/unknown");
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,9 @@
|
||||||
pub mod anthropic;
|
pub mod anthropic;
|
||||||
pub mod openai;
|
pub mod openai;
|
||||||
|
|
||||||
// Re-export all types for convenience
|
|
||||||
pub use anthropic::*;
|
pub use anthropic::*;
|
||||||
pub use openai::*;
|
pub use openai::*;
|
||||||
|
|
||||||
/// Common trait that all API definitions must implement
|
|
||||||
///
|
|
||||||
/// This trait ensures consistency across different AI provider API definitions
|
|
||||||
/// and makes it easy to add new providers like Gemini, Claude, etc.
|
|
||||||
///
|
|
||||||
/// Note: This is different from the `ApiProvider` enum in `clients::endpoints`
|
|
||||||
/// which represents provider identification, while this trait defines API capabilities.
|
|
||||||
///
|
|
||||||
/// # Benefits
|
|
||||||
///
|
|
||||||
/// - **Consistency**: All API providers implement the same interface
|
|
||||||
/// - **Extensibility**: Easy to add new providers without breaking existing code
|
|
||||||
/// - **Type Safety**: Compile-time guarantees that all providers implement required methods
|
|
||||||
/// - **Discoverability**: Clear documentation of what capabilities each API supports
|
|
||||||
///
|
|
||||||
/// # Example implementation for a new provider:
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// use serde::{Deserialize, Serialize};
|
|
||||||
/// use super::ApiDefinition;
|
|
||||||
///
|
|
||||||
/// #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
/// pub enum GeminiApi {
|
|
||||||
/// GenerateContent,
|
|
||||||
/// ChatCompletions,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl GeminiApi {
|
|
||||||
/// pub fn endpoint(&self) -> &'static str {
|
|
||||||
/// match self {
|
|
||||||
/// GeminiApi::GenerateContent => "/v1/models/gemini-pro:generateContent",
|
|
||||||
/// GeminiApi::ChatCompletions => "/v1/models/gemini-pro:chat",
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// pub fn from_endpoint(endpoint: &str) -> Option<Self> {
|
|
||||||
/// match endpoint {
|
|
||||||
/// "/v1/models/gemini-pro:generateContent" => Some(GeminiApi::GenerateContent),
|
|
||||||
/// "/v1/models/gemini-pro:chat" => Some(GeminiApi::ChatCompletions),
|
|
||||||
/// _ => None,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// pub fn supports_streaming(&self) -> bool {
|
|
||||||
/// match self {
|
|
||||||
/// GeminiApi::GenerateContent => true,
|
|
||||||
/// GeminiApi::ChatCompletions => true,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// pub fn supports_tools(&self) -> bool {
|
|
||||||
/// match self {
|
|
||||||
/// GeminiApi::GenerateContent => true,
|
|
||||||
/// GeminiApi::ChatCompletions => false,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// pub fn supports_vision(&self) -> bool {
|
|
||||||
/// match self {
|
|
||||||
/// GeminiApi::GenerateContent => true,
|
|
||||||
/// GeminiApi::ChatCompletions => false,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// impl ApiDefinition for GeminiApi {
|
|
||||||
/// fn endpoint(&self) -> &'static str {
|
|
||||||
/// self.endpoint()
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn from_endpoint(endpoint: &str) -> Option<Self> {
|
|
||||||
/// Self::from_endpoint(endpoint)
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn supports_streaming(&self) -> bool {
|
|
||||||
/// self.supports_streaming()
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn supports_tools(&self) -> bool {
|
|
||||||
/// self.supports_tools()
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn supports_vision(&self) -> bool {
|
|
||||||
/// self.supports_vision()
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Now you can use generic code that works with any API:
|
|
||||||
/// fn print_api_info<T: ApiDefinition>(api: &T) {
|
|
||||||
/// println!("Endpoint: {}", api.endpoint());
|
|
||||||
/// println!("Supports streaming: {}", api.supports_streaming());
|
|
||||||
/// println!("Supports tools: {}", api.supports_tools());
|
|
||||||
/// println!("Supports vision: {}", api.supports_vision());
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // Works with both OpenAI and Anthropic (and future Gemini)
|
|
||||||
/// print_api_info(&OpenAIApi::ChatCompletions);
|
|
||||||
/// print_api_info(&AnthropicApi::Messages);
|
|
||||||
/// print_api_info(&GeminiApi::GenerateContent);
|
|
||||||
/// ```
|
|
||||||
pub trait ApiDefinition {
|
pub trait ApiDefinition {
|
||||||
/// Returns the endpoint path for this API
|
/// Returns the endpoint path for this API
|
||||||
fn endpoint(&self) -> &'static str;
|
fn endpoint(&self) -> &'static str;
|
||||||
|
|
@ -132,6 +31,7 @@ pub trait ApiDefinition {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::{CHAT_COMPLETIONS_PATH, MESSAGES_PATH};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generic_api_functionality() {
|
fn test_generic_api_functionality() {
|
||||||
|
|
@ -150,8 +50,8 @@ mod tests {
|
||||||
fn test_api_detection_from_endpoints() {
|
fn test_api_detection_from_endpoints() {
|
||||||
// Test that we can detect APIs from endpoints using the trait
|
// Test that we can detect APIs from endpoints using the trait
|
||||||
let endpoints = vec![
|
let endpoints = vec![
|
||||||
"/v1/chat/completions",
|
CHAT_COMPLETIONS_PATH,
|
||||||
"/v1/messages",
|
MESSAGES_PATH,
|
||||||
"/v1/unknown"
|
"/v1/unknown"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::providers::request::{ProviderRequest, ProviderRequestError};
|
||||||
use crate::providers::response::{ProviderResponse, ProviderStreamResponse, TokenUsage};
|
use crate::providers::response::{ProviderResponse, ProviderStreamResponse, TokenUsage};
|
||||||
use super::ApiDefinition;
|
use super::ApiDefinition;
|
||||||
use crate::clients::transformer::{ExtractText};
|
use crate::clients::transformer::{ExtractText};
|
||||||
|
use crate::{CHAT_COMPLETIONS_PATH};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// OPENAI API ENUMERATION
|
// OPENAI API ENUMERATION
|
||||||
|
|
@ -27,13 +28,13 @@ pub enum OpenAIApi {
|
||||||
impl ApiDefinition for OpenAIApi {
|
impl ApiDefinition for OpenAIApi {
|
||||||
fn endpoint(&self) -> &'static str {
|
fn endpoint(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
OpenAIApi::ChatCompletions => "/v1/chat/completions",
|
OpenAIApi::ChatCompletions => CHAT_COMPLETIONS_PATH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_endpoint(endpoint: &str) -> Option<Self> {
|
fn from_endpoint(endpoint: &str) -> Option<Self> {
|
||||||
match endpoint {
|
match endpoint {
|
||||||
"/v1/chat/completions" => Some(OpenAIApi::ChatCompletions),
|
CHAT_COMPLETIONS_PATH => Some(OpenAIApi::ChatCompletions),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -943,13 +944,13 @@ mod tests {
|
||||||
let api = OpenAIApi::ChatCompletions;
|
let api = OpenAIApi::ChatCompletions;
|
||||||
|
|
||||||
// Test trait methods
|
// Test trait methods
|
||||||
assert_eq!(api.endpoint(), "/v1/chat/completions");
|
assert_eq!(api.endpoint(), CHAT_COMPLETIONS_PATH);
|
||||||
assert!(api.supports_streaming());
|
assert!(api.supports_streaming());
|
||||||
assert!(api.supports_tools());
|
assert!(api.supports_tools());
|
||||||
assert!(api.supports_vision());
|
assert!(api.supports_vision());
|
||||||
|
|
||||||
// Test from_endpoint
|
// Test from_endpoint
|
||||||
let found_api = OpenAIApi::from_endpoint("/v1/chat/completions");
|
let found_api = OpenAIApi::from_endpoint(CHAT_COMPLETIONS_PATH);
|
||||||
assert_eq!(found_api, Some(OpenAIApi::ChatCompletions));
|
assert_eq!(found_api, Some(OpenAIApi::ChatCompletions));
|
||||||
|
|
||||||
let not_found = OpenAIApi::from_endpoint("/v1/unknown");
|
let not_found = OpenAIApi::from_endpoint("/v1/unknown");
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,18 @@
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
pub mod apis;
|
pub mod apis;
|
||||||
pub mod clients;
|
pub mod clients;
|
||||||
|
|
||||||
// Re-export important types and traits
|
// Re-export important types and traits
|
||||||
pub use providers::request::{ProviderRequestType, ProviderRequest, ProviderRequestError};
|
pub use providers::request::{ProviderRequestType, ProviderRequest, ProviderRequestError};
|
||||||
pub use providers::response::{ProviderResponseType, ProviderStreamResponseType, ProviderResponse, ProviderStreamResponse, ProviderResponseError, TokenUsage, SseEvent, SseStreamIter};
|
pub use providers::response::{ProviderResponseType, ProviderStreamResponseType, ProviderResponse, ProviderStreamResponse, ProviderResponseError, TokenUsage, SseEvent, SseStreamIter};
|
||||||
pub use providers::id::ProviderId;
|
pub use providers::id::ProviderId;
|
||||||
pub use providers::adapters::{has_compatible_api, supported_apis};
|
pub use providers::adapters::{has_compatible_api, supported_apis};
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: Refactor such that commons doesn't depend on Hermes. For now this will clean up strings
|
||||||
|
pub const CHAT_COMPLETIONS_PATH: &str = "/v1/chat/completions";
|
||||||
|
pub const MESSAGES_PATH: &str = "/v1/messages";
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -25,17 +30,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_provider_api_compatibility() {
|
fn test_provider_api_compatibility() {
|
||||||
assert!(has_compatible_api(&ProviderId::OpenAI, "/v1/chat/completions"));
|
assert!(has_compatible_api(&ProviderId::OpenAI, CHAT_COMPLETIONS_PATH));
|
||||||
assert!(!has_compatible_api(&ProviderId::OpenAI, "/v1/embeddings"));
|
assert!(!has_compatible_api(&ProviderId::OpenAI, "/v1/embeddings"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_provider_supported_apis() {
|
fn test_provider_supported_apis() {
|
||||||
let apis = supported_apis(&ProviderId::OpenAI);
|
let apis = supported_apis(&ProviderId::OpenAI);
|
||||||
assert!(apis.contains(&"/v1/chat/completions"));
|
assert!(apis.contains(&CHAT_COMPLETIONS_PATH));
|
||||||
|
|
||||||
// Test that provider supports the expected API endpoints
|
// Test that provider supports the expected API endpoints
|
||||||
assert!(has_compatible_api(&ProviderId::OpenAI, "/v1/chat/completions"));
|
assert!(has_compatible_api(&ProviderId::OpenAI, CHAT_COMPLETIONS_PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
//! Provider adapter configuration and API compatibility utilities.
|
|
||||||
//
|
|
||||||
// Note: For all request/response conversions between Anthropic and OpenAI APIs,
|
|
||||||
// use the peer-reviewed and well-tested implementations in `clients/transformer.rs`.
|
|
||||||
// This file should not contain conversion logic.
|
|
||||||
|
|
||||||
/// Utility to check if a model is from the Claude/Anthropic family
|
|
||||||
pub fn is_claude_family(model: &str) -> bool {
|
|
||||||
let model = model.to_lowercase();
|
|
||||||
model.contains("claude") || model.contains("anthropic")
|
|
||||||
}
|
|
||||||
use crate::providers::id::ProviderId;
|
use crate::providers::id::ProviderId;
|
||||||
|
use crate::{CHAT_COMPLETIONS_PATH, MESSAGES_PATH};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AdapterType {
|
pub enum AdapterType {
|
||||||
|
|
@ -43,13 +33,13 @@ pub fn get_provider_config(provider_id: &ProviderId) -> ProviderConfig {
|
||||||
ProviderId::OpenAI | ProviderId::Groq | ProviderId::Mistral | ProviderId::Deepseek
|
ProviderId::OpenAI | ProviderId::Groq | ProviderId::Mistral | ProviderId::Deepseek
|
||||||
| ProviderId::Arch | ProviderId::Gemini | ProviderId::GitHub => {
|
| ProviderId::Arch | ProviderId::Gemini | ProviderId::GitHub => {
|
||||||
ProviderConfig {
|
ProviderConfig {
|
||||||
supported_apis: &["/v1/chat/completions"],
|
supported_apis: &[CHAT_COMPLETIONS_PATH],
|
||||||
adapter_type: AdapterType::OpenAICompatible,
|
adapter_type: AdapterType::OpenAICompatible,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProviderId::Anthropic => {
|
ProviderId::Anthropic => {
|
||||||
ProviderConfig {
|
ProviderConfig {
|
||||||
supported_apis: &["/v1/messages", "/v1/chat/completions"],
|
supported_apis: &[MESSAGES_PATH],
|
||||||
adapter_type: AdapterType::AnthropicCompatible,
|
adapter_type: AdapterType::AnthropicCompatible,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,31 +148,32 @@ impl fmt::Display for SseEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Into implementation to convert SseEvent to bytes for response buffer
|
||||||
|
impl Into<Vec<u8>> for SseEvent {
|
||||||
|
fn into(self) -> Vec<u8> {
|
||||||
|
format!("{}\n\n", self.raw_line).into_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Response transformation logic for client API compatibility ---
|
// --- Response transformation logic for client API compatibility ---
|
||||||
impl TryFrom<(&[u8], &SupportedAPIs, &ProviderId)> for ProviderResponseType {
|
impl TryFrom<(&[u8], &SupportedAPIs, &ProviderId)> for ProviderResponseType {
|
||||||
type Error = std::io::Error;
|
type Error = std::io::Error;
|
||||||
|
|
||||||
fn try_from((bytes, client_api, provider_id): (&[u8], &SupportedAPIs, &ProviderId)) -> Result<Self, Self::Error> {
|
fn try_from((bytes, client_api, provider_id): (&[u8], &SupportedAPIs, &ProviderId)) -> Result<Self, Self::Error> {
|
||||||
let upstream_api = provider_id.compatible_api_for_client(client_api);
|
let upstream_api = provider_id.compatible_api_for_client(client_api);
|
||||||
|
|
||||||
// Step 1: Parse bytes using upstream API format (what the provider actually sent)
|
|
||||||
// Step 2: Return response type that matches client API format (what client expects)
|
|
||||||
match (&upstream_api, client_api) {
|
match (&upstream_api, client_api) {
|
||||||
// Upstream sent OpenAI format, client expects OpenAI format - direct pass-through
|
|
||||||
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
||||||
let resp: ChatCompletionsResponse = ChatCompletionsResponse::try_from(bytes)
|
let resp: ChatCompletionsResponse = ChatCompletionsResponse::try_from(bytes)
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||||
Ok(ProviderResponseType::ChatCompletionsResponse(resp))
|
Ok(ProviderResponseType::ChatCompletionsResponse(resp))
|
||||||
}
|
}
|
||||||
// Upstream sent Anthropic format, client expects Anthropic format - direct pass-through
|
|
||||||
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
||||||
let resp: MessagesResponse = serde_json::from_slice(bytes)
|
let resp: MessagesResponse = serde_json::from_slice(bytes)
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||||
Ok(ProviderResponseType::MessagesResponse(resp))
|
Ok(ProviderResponseType::MessagesResponse(resp))
|
||||||
}
|
}
|
||||||
// Upstream sent Anthropic format, client expects OpenAI format - need transformation
|
|
||||||
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
||||||
// Parse as Anthropic Messages response first
|
|
||||||
let anthropic_resp: MessagesResponse = serde_json::from_slice(bytes)
|
let anthropic_resp: MessagesResponse = serde_json::from_slice(bytes)
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||||
|
|
||||||
|
|
@ -181,9 +182,7 @@ impl TryFrom<(&[u8], &SupportedAPIs, &ProviderId)> for ProviderResponseType {
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Transformation error: {}", e)))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Transformation error: {}", e)))?;
|
||||||
Ok(ProviderResponseType::ChatCompletionsResponse(chat_resp))
|
Ok(ProviderResponseType::ChatCompletionsResponse(chat_resp))
|
||||||
}
|
}
|
||||||
// Upstream sent OpenAI format, client expects Anthropic format - need transformation
|
|
||||||
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
||||||
// Parse as OpenAI ChatCompletions response first
|
|
||||||
let openai_resp: ChatCompletionsResponse = ChatCompletionsResponse::try_from(bytes)
|
let openai_resp: ChatCompletionsResponse = ChatCompletionsResponse::try_from(bytes)
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||||
|
|
||||||
|
|
@ -202,32 +201,23 @@ impl TryFrom<(&[u8], &SupportedAPIs, &ProviderId)> for ProviderStreamResponseTyp
|
||||||
|
|
||||||
fn try_from((bytes, client_api, provider_id): (&[u8], &SupportedAPIs, &ProviderId)) -> Result<Self, Self::Error> {
|
fn try_from((bytes, client_api, provider_id): (&[u8], &SupportedAPIs, &ProviderId)) -> Result<Self, Self::Error> {
|
||||||
let upstream_api = provider_id.compatible_api_for_client(client_api);
|
let upstream_api = provider_id.compatible_api_for_client(client_api);
|
||||||
|
|
||||||
// Step 1: Parse bytes using upstream API format (what the provider actually sent)
|
|
||||||
// Step 2: Return response type that matches client API format (what client expects)
|
|
||||||
match (&upstream_api, client_api) {
|
match (&upstream_api, client_api) {
|
||||||
// Upstream sent OpenAI format, client expects OpenAI format - direct pass-through
|
|
||||||
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
||||||
let resp: crate::apis::openai::ChatCompletionsStreamResponse = serde_json::from_slice(bytes)?;
|
let resp: crate::apis::openai::ChatCompletionsStreamResponse = serde_json::from_slice(bytes)?;
|
||||||
Ok(ProviderStreamResponseType::ChatCompletionsStreamResponse(resp))
|
Ok(ProviderStreamResponseType::ChatCompletionsStreamResponse(resp))
|
||||||
}
|
}
|
||||||
// Upstream sent Anthropic format, client expects Anthropic format - direct pass-through
|
|
||||||
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
||||||
let resp: crate::apis::anthropic::MessagesStreamEvent = serde_json::from_slice(bytes)?;
|
let resp: crate::apis::anthropic::MessagesStreamEvent = serde_json::from_slice(bytes)?;
|
||||||
Ok(ProviderStreamResponseType::MessagesStreamEvent(resp))
|
Ok(ProviderStreamResponseType::MessagesStreamEvent(resp))
|
||||||
}
|
}
|
||||||
// Upstream sent Anthropic format, client expects OpenAI format - need transformation
|
|
||||||
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
(SupportedAPIs::AnthropicMessagesAPI(_), SupportedAPIs::OpenAIChatCompletions(_)) => {
|
||||||
// Parse as Anthropic Messages stream event first
|
|
||||||
let anthropic_resp: crate::apis::anthropic::MessagesStreamEvent = serde_json::from_slice(bytes)?;
|
let anthropic_resp: crate::apis::anthropic::MessagesStreamEvent = serde_json::from_slice(bytes)?;
|
||||||
|
|
||||||
// Transform to OpenAI ChatCompletions stream format using the transformer
|
// Transform to OpenAI ChatCompletions stream format using the transformer
|
||||||
let chat_resp: crate::apis::openai::ChatCompletionsStreamResponse = anthropic_resp.try_into()?;
|
let chat_resp: crate::apis::openai::ChatCompletionsStreamResponse = anthropic_resp.try_into()?;
|
||||||
Ok(ProviderStreamResponseType::ChatCompletionsStreamResponse(chat_resp))
|
Ok(ProviderStreamResponseType::ChatCompletionsStreamResponse(chat_resp))
|
||||||
}
|
}
|
||||||
// Upstream sent OpenAI format, client expects Anthropic format - need transformation
|
|
||||||
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
(SupportedAPIs::OpenAIChatCompletions(_), SupportedAPIs::AnthropicMessagesAPI(_)) => {
|
||||||
// Parse as OpenAI ChatCompletions stream response first
|
|
||||||
let openai_resp: crate::apis::openai::ChatCompletionsStreamResponse = serde_json::from_slice(bytes)?;
|
let openai_resp: crate::apis::openai::ChatCompletionsStreamResponse = serde_json::from_slice(bytes)?;
|
||||||
|
|
||||||
// Transform to Anthropic Messages stream format using the transformer
|
// Transform to Anthropic Messages stream format using the transformer
|
||||||
|
|
@ -304,12 +294,6 @@ impl TryFrom<(SseEvent, &SupportedAPIs, &SupportedAPIs)> for SseEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Into implementation to convert SseEvent to bytes for response buffer
|
|
||||||
impl Into<Vec<u8>> for SseEvent {
|
|
||||||
fn into(self) -> Vec<u8> {
|
|
||||||
format!("{}\n\n", self.raw_line).into_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue