providers-kit 0.0.2

Portable enum-dispatch Kit over multiple model provider APIs.
Documentation
use std::time::Instant;

use reqwest::Method;

use crate::KitError;
use crate::config::OllamaConfig;
use crate::config::TransportConfig;
use crate::error::{ErrorPhase, ResponseViolation};
use crate::models::ModelId;
use crate::models::{ModelListRequest, ModelPage, ModelSummary};
use crate::provider::ProviderBackend;
use crate::runtime::HealthStatus;
use crate::transport::CheckedRequestSpec;

use super::api::{OLLAMA_REQUESTS, OllamaApiContext};
use super::client::OllamaClient;
use super::models::OllamaModel;
use super::wire::OllamaVersionResponse;

#[derive(Clone)]
pub(crate) struct OllamaBackend {
    client: OllamaClient,
}

impl OllamaBackend {
    pub(crate) fn from_config(
        config: OllamaConfig,
        transport: TransportConfig,
    ) -> Result<Self, KitError> {
        Ok(Self {
            client: OllamaClient::new(OllamaApiContext::shared(config, transport)?),
        })
    }
}

impl ProviderBackend for OllamaBackend {
    type Model = OllamaModel;

    fn model(&self, id: ModelId) -> Result<OllamaModel, KitError> {
        Ok(OllamaModel::new(self.client.clone(), id))
    }

    async fn list_models(&self, request: ModelListRequest) -> Result<ModelPage, KitError> {
        super::models::list_models(self.client.api(), request).await
    }

    async fn model_info(&self, id: ModelId) -> Result<ModelSummary, KitError> {
        super::models::model_info(self.client.api(), id).await
    }

    async fn health(&self) -> Result<HealthStatus, KitError> {
        let api = self.client.api();
        let start = Instant::now();
        let version = OLLAMA_REQUESTS
            .json::<OllamaVersionResponse>(
                "version",
                api.request(Method::GET, "version")?,
                api.transport().execution(),
            )
            .await?;
        version.normalize(|version| {
            if version.version.trim().is_empty() {
                return Err(KitError::invalid_response(ResponseViolation::InvalidField {
                    field: "version",
                })
                .at_phase(ErrorPhase::Normalize)
                .with_message("Ollama health response contained an empty version".to_string())
                .with_diagnostic(CheckedRequestSpec::diagnostic_json(&version)));
            }
            Ok(HealthStatus {
                latency: start.elapsed(),
            })
        })
    }

    async fn shutdown(&self) -> Result<(), KitError> {
        Ok(())
    }
}