gateway-core 0.3.1

Runtime-neutral LLM provider adapters, wire translation, and routing primitives for the Axond AI gateway.
Documentation
use crate::ProviderError;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FailoverTarget {
    pub provider: String,
    pub model: String,
}

impl FailoverTarget {
    pub fn new(provider: impl Into<String>, model: impl Into<String>) -> Self {
        Self {
            provider: provider.into(),
            model: model.into(),
        }
    }

    pub fn qualified_model(&self) -> String {
        format!("{}/{}", self.provider, self.model)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FailoverDecision {
    TryNext,
    Return,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct FailoverPolicy;

impl FailoverPolicy {
    pub fn decide(&self, error: &ProviderError, has_next: bool) -> FailoverDecision {
        if has_next && error.is_retryable() {
            FailoverDecision::TryNext
        } else {
            FailoverDecision::Return
        }
    }

    pub fn ordered_targets(
        &self,
        targets: impl IntoIterator<Item = FailoverTarget>,
    ) -> Result<Vec<FailoverTarget>, ProviderError> {
        let targets: Vec<_> = targets.into_iter().collect();
        if targets.is_empty() {
            Err(ProviderError::InvalidRequest(
                "at least one provider target is required".into(),
            ))
        } else {
            Ok(targets)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn retries_only_retryable_errors_with_remaining_target() {
        let policy = FailoverPolicy;
        assert_eq!(
            policy.decide(&ProviderError::transport("openai", "timeout"), true),
            FailoverDecision::TryNext
        );
        assert_eq!(
            policy.decide(&ProviderError::InvalidRequest("bad".into()), true),
            FailoverDecision::Return
        );
        assert_eq!(
            policy.decide(&ProviderError::transport("openai", "timeout"), false),
            FailoverDecision::Return
        );
    }
}