remem-ai 0.6.38

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use super::{
    configured_api_key, embed_openai, EmbeddingConfig, EmbeddingProvider, EmbeddingProviderStatus,
    FEATURE_HASH_EMBEDDING_DIMENSIONS, FEATURE_HASH_EMBEDDING_MODEL,
};

#[derive(Debug)]
struct EmbeddingProviderOffError {
    cause: Option<String>,
}

impl std::fmt::Display for EmbeddingProviderOffError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.cause.as_deref() {
            Some(cause) => write!(f, "embedding provider is off: {cause}"),
            None => f.write_str("embedding provider is off"),
        }
    }
}

impl std::error::Error for EmbeddingProviderOffError {}

pub(crate) fn is_embedding_provider_off_error(error: &anyhow::Error) -> bool {
    error
        .downcast_ref::<EmbeddingProviderOffError>()
        .is_some_and(|error| error.cause.is_none())
}

pub(super) fn embedding_provider_off_error() -> anyhow::Error {
    EmbeddingProviderOffError { cause: None }.into()
}

pub(super) fn embedding_provider_off_error_with_cause(cause: String) -> anyhow::Error {
    EmbeddingProviderOffError { cause: Some(cause) }.into()
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ProviderRuntime {
    pub(super) provider: EmbeddingProvider,
    pub(super) model_id: Option<String>,
    pub(super) dimensions: Option<usize>,
    pub(super) disabled: bool,
    pub(super) unavailable_reason: Option<String>,
    pub(super) degradation_reason: Option<String>,
}

pub(super) fn resolve_provider_status(config: &EmbeddingConfig) -> EmbeddingProviderStatus {
    let configured = config.provider;
    let mut runtime = provider_runtime(config, configured);
    let mut degraded = runtime.degradation_reason.is_some();
    let mut degradation_reason = runtime.degradation_reason.clone();
    if let Some(reason) = degradation_reason.as_deref() {
        crate::log::error("embedding", reason);
    }

    if let Some(reason) = runtime.unavailable_reason.clone() {
        degraded = true;
        if let Some(fallback) = config.fallback {
            let fallback_runtime = provider_runtime(config, fallback);
            if fallback == EmbeddingProvider::Off {
                let message = format!(
                    "configured embedding provider {} unavailable: {}; using fallback off; fallback off disabled provider fallback",
                    configured.label(),
                    reason
                );
                degradation_reason = Some(message.clone());
                runtime = disabled_runtime();
            } else if fallback_runtime.unavailable_reason.is_none() {
                let message = format!(
                    "configured embedding provider {} unavailable: {}; using fallback {}",
                    configured.label(),
                    reason,
                    fallback.label()
                );
                crate::log::error("embedding", &message);
                degradation_reason = Some(message);
                runtime = fallback_runtime;
            } else {
                degradation_reason = Some(format!(
                    "configured embedding provider {} unavailable: {}; fallback {} unavailable: {}",
                    configured.label(),
                    reason,
                    fallback.label(),
                    fallback_runtime
                        .unavailable_reason
                        .as_deref()
                        .unwrap_or("unknown")
                ));
            }
        } else {
            degradation_reason = Some(format!(
                "configured embedding provider {} unavailable: {}",
                configured.label(),
                reason
            ));
        }
    }

    EmbeddingProviderStatus {
        configured_provider: configured.label().to_string(),
        fallback_provider: config.fallback.map(|provider| provider.label().to_string()),
        active_provider: runtime.provider.label().to_string(),
        active_model_id: runtime.model_id,
        active_dimensions: runtime.dimensions,
        degraded,
        disabled: runtime.disabled,
        unavailable_reason: runtime.unavailable_reason,
        degradation_reason,
        model_dir: Some(
            super::local_semantic::model_root(config)
                .display()
                .to_string(),
        ),
    }
}

pub(super) fn probe_active_api_profile(
    config: &EmbeddingConfig,
    status: &mut EmbeddingProviderStatus,
) {
    if status.unavailable_reason.is_some()
        || status.active_provider != EmbeddingProvider::OpenAi.label()
    {
        return;
    }
    let api_key = match configured_api_key(config) {
        Ok(Some(api_key)) => api_key,
        Ok(None) => {
            status.unavailable_reason = Some(format!(
                "requires {} or {}",
                super::ENV_API_KEY,
                config.api_key_env
            ));
            status.active_model_id = None;
            status.active_dimensions = None;
            return;
        }
        Err(error) => {
            status.unavailable_reason = Some(error.to_string());
            status.active_model_id = None;
            status.active_dimensions = None;
            return;
        }
    };
    match embed_openai("remem embedding profile probe", config, &api_key) {
        Ok(profile) => {
            status.active_model_id = Some(profile.model().to_string());
            status.active_dimensions = Some(profile.dimensions());
        }
        Err(error) => apply_api_probe_failure_status(config, status, error),
    }
}

fn apply_api_probe_failure_status(
    config: &EmbeddingConfig,
    status: &mut EmbeddingProviderStatus,
    error: anyhow::Error,
) {
    if let Some(fallback) = config.fallback {
        let fallback_runtime = provider_runtime(config, fallback);
        status.degraded = true;
        if fallback == EmbeddingProvider::Off {
            let message = format!(
                "configured embedding provider api unavailable: {}; using fallback off; fallback off disabled provider fallback",
                error
            );
            status.degradation_reason = Some(message.clone());
            status.active_provider = EmbeddingProvider::Off.label().to_string();
            status.active_model_id = None;
            status.active_dimensions = None;
            status.disabled = true;
            status.unavailable_reason = None;
        } else if fallback_runtime.unavailable_reason.is_none() {
            let message = format!(
                "configured embedding provider api unavailable: {}; using fallback {}",
                error,
                fallback.label()
            );
            crate::log::error("embedding", &message);
            status.degradation_reason = Some(message);
            status.active_provider = fallback_runtime.provider.label().to_string();
            status.active_model_id = fallback_runtime.model_id;
            status.active_dimensions = fallback_runtime.dimensions;
            status.disabled = fallback_runtime.disabled;
            status.unavailable_reason = fallback_runtime.unavailable_reason;
        } else {
            status.degradation_reason = Some(format!(
                "configured embedding provider api unavailable: {}; fallback {} unavailable: {}",
                error,
                fallback.label(),
                fallback_runtime
                    .unavailable_reason
                    .as_deref()
                    .unwrap_or("unknown")
            ));
            status.active_model_id = None;
            status.active_dimensions = None;
            status.disabled = false;
            status.unavailable_reason = fallback_runtime.unavailable_reason;
        }
        return;
    }

    status.unavailable_reason = Some(format!("embedding provider api unavailable: {error}"));
    status.active_model_id = None;
    status.active_dimensions = None;
}

pub(super) fn provider_runtime(
    config: &EmbeddingConfig,
    provider: EmbeddingProvider,
) -> ProviderRuntime {
    match provider {
        EmbeddingProvider::Auto => match super::auto_api_key(config) {
            Ok(Some(_)) => provider_runtime(config, EmbeddingProvider::OpenAi),
            Ok(None) => match super::local_semantic::auto_installed_model_profile(config) {
                Ok(Some(profile)) => ProviderRuntime {
                    provider: EmbeddingProvider::Local,
                    model_id: Some(profile.model),
                    dimensions: Some(profile.dimensions),
                    disabled: false,
                    unavailable_reason: None,
                    degradation_reason: None,
                },
                Ok(None) => provider_runtime(config, EmbeddingProvider::FeatureHash),
                Err(error) => {
                    let mut runtime = provider_runtime(config, EmbeddingProvider::FeatureHash);
                    runtime.degradation_reason = Some(format!(
                        "automatic local embedding provider unavailable: {error}; using feature-hash"
                    ));
                    runtime
                }
            },
            Err(error) => unavailable_runtime(provider, error.to_string()),
        },
        EmbeddingProvider::Local => match super::local_semantic::installed_model_profile(config) {
            Ok(profile) => ProviderRuntime {
                provider: EmbeddingProvider::Local,
                model_id: Some(profile.model),
                dimensions: Some(profile.dimensions),
                disabled: false,
                unavailable_reason: None,
                degradation_reason: None,
            },
            Err(error) => unavailable_runtime(provider, error.to_string()),
        },
        EmbeddingProvider::FeatureHash => ProviderRuntime {
            provider: EmbeddingProvider::FeatureHash,
            model_id: Some(FEATURE_HASH_EMBEDDING_MODEL.to_string()),
            dimensions: Some(FEATURE_HASH_EMBEDDING_DIMENSIONS),
            disabled: false,
            unavailable_reason: None,
            degradation_reason: None,
        },
        EmbeddingProvider::OpenAi => match configured_api_key(config) {
            Ok(Some(_)) => ProviderRuntime {
                provider: EmbeddingProvider::OpenAi,
                model_id: Some(config.model.clone()),
                dimensions: config.dimensions,
                disabled: false,
                unavailable_reason: None,
                degradation_reason: None,
            },
            Ok(None) => unavailable_runtime(
                provider,
                format!("requires {} or {}", super::ENV_API_KEY, config.api_key_env),
            ),
            Err(error) => unavailable_runtime(provider, error.to_string()),
        },
        EmbeddingProvider::Off => ProviderRuntime {
            provider: EmbeddingProvider::Off,
            model_id: None,
            dimensions: None,
            disabled: true,
            unavailable_reason: None,
            degradation_reason: None,
        },
    }
}

fn unavailable_runtime(provider: EmbeddingProvider, reason: String) -> ProviderRuntime {
    ProviderRuntime {
        provider,
        model_id: None,
        dimensions: None,
        disabled: false,
        unavailable_reason: Some(reason),
        degradation_reason: None,
    }
}

fn disabled_runtime() -> ProviderRuntime {
    ProviderRuntime {
        provider: EmbeddingProvider::Off,
        model_id: None,
        dimensions: None,
        disabled: true,
        unavailable_reason: None,
        degradation_reason: None,
    }
}