omniference 0.1.8

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

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

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

/// 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()
	}
}