omniference 0.1.4

A multi-protocol inference engine with provider adapters
Documentation
pub mod openai;
pub mod context;

pub use openai::*;
pub use context::*;

use axum::{response::Response, response::IntoResponse};
use crate::types::ModelRef;

/// Trait for converting external API request formats to internal IR.
/// 
/// This is analogous to `ChatAdapter` but works in the opposite direction:
/// - **Skin**: External API format → Internal IR (inbound)
/// - **Adapter**: Internal IR → Provider API format (outbound)
/// 
/// Each skin implementation handles a specific external API format
/// (e.g., OpenAI Chat Completions, OpenAI Responses API, future Anthropic API, etc.)
pub trait Skin: Send + Sync {
    /// The external request type this skin handles (e.g., OpenAIChatRequest)
    type Request: serde::de::DeserializeOwned + Send + Clone;
    
    /// Convert an external API request to internal IR format.
    /// 
    /// # Arguments
    /// * `req` - The deserialized external request
    /// * `model` - The resolved model reference
    /// 
    /// # Returns
    /// The internal ChatRequestIR that can be processed by adapters
    fn external_to_ir(
        req: Self::Request,
        model: ModelRef,
    ) -> anyhow::Result<crate::ChatRequestIR>;
    
    /// Get the error handler for this skin's response format
    fn error_handler() -> &'static dyn SkinErrorHandler;
    
    /// Unique identifier for this skin (for logging/debugging)
    fn skin_id() -> &'static str;
}

/// Trait for skin-specific error handling
pub trait SkinErrorHandler {
    /// Handle JSON deserialization errors for this skin
    fn handle_json_error(&self, error: serde_json::Error) -> Response;
    
    /// Handle not found errors for this skin
    fn handle_not_found(&self) -> Response;
    
    /// Handle method not allowed errors for this skin
    fn handle_method_not_allowed(&self) -> Response;
    
    /// Handle model not found errors for this skin
    fn handle_model_not_found(&self, model_name: &str) -> Response;

    /// Handle provider errors for this skin
    fn handle_provider_error(&self, code: String, message: String) -> Response;
}

/// OpenAI skin error handler
pub struct OpenAIErrorHandler;

impl SkinErrorHandler for OpenAIErrorHandler {
    fn handle_json_error(&self, error: serde_json::Error) -> Response {
        eprintln!("Error: {}", error);
        let error_msg = if error.to_string().contains("model") && error.to_string().contains("required") {
            "Missing required parameter: 'model'.".to_string()
        } else if error.to_string().contains("input") && error.to_string().contains("required") {
            "Missing required parameter: 'input'.".to_string()
        } else if error.to_string().contains("messages") && error.to_string().contains("required") {
            "Missing required parameter: 'messages'.".to_string()
        } else if error.to_string().contains("max_tokens") && error.to_string().contains("u32") {
            "Invalid value for 'max_tokens'. Must be a positive integer.".to_string()
        } else if error.to_string().contains("temperature") {
            "Invalid value for 'temperature'. Must be between 0 and 2.".to_string()
        } else if error.to_string().contains("top_p") {
            "Invalid value for 'top_p'. Must be between 0 and 1.".to_string()
        } else {
            format!("Failed to parse request body: {}", error)
        };

        let error = serde_json::json!({
            "error": {
                "message": error_msg,
                "type": "invalid_request_error",
                "code": "invalid_request_body"
            }
        });
        (
            axum::http::StatusCode::BAD_REQUEST,
            axum::Json(error)
        ).into_response()
    }

    fn handle_not_found(&self) -> Response {
        let error = serde_json::json!({
            "error": {
                "message": "The requested resource was not found",
                "type": "not_found_error",
                "code": "not_found"
            }
        });
        (
            axum::http::StatusCode::NOT_FOUND,
            axum::Json(error)
        ).into_response()
    }

    fn handle_method_not_allowed(&self) -> Response {
        let error = serde_json::json!({
            "error": {
                "message": "Invalid HTTP method. This endpoint requires POST or PUT.",
                "type": "invalid_request_error",
                "code": "method_not_allowed"
            }
        });
        (
            axum::http::StatusCode::METHOD_NOT_ALLOWED,
            axum::Json(error)
        ).into_response()
    }

    fn handle_model_not_found(&self, model_name: &str) -> Response {
        let error = serde_json::json!({
            "error": {
                "message": format!("Model '{}' not found", model_name),
                "type": "invalid_request_error",
                "code": "model_not_found"
            }
        });
        (
            axum::http::StatusCode::NOT_FOUND,
            axum::Json(error)
        ).into_response()
    }

    fn handle_provider_error(&self, code: String, message: String) -> Response {
        let error = serde_json::json!({
            "error": {
                "message": message,
                "type": "provider_error",
                "code": code
            }
        });
        (
            axum::http::StatusCode::INTERNAL_SERVER_ERROR,
            axum::Json(error)
        ).into_response()
    }
}