frigg 0.3.2

Local-first MCP server for code understanding.
Documentation
use super::*;
use crate::mcp::server::runtime_cache::serialized_value_estimated_bytes;

impl FriggMcpServer {
    pub(crate) fn invalidate_repository_search_response_caches(&self, repository_id: &str) {
        let mut search_text_cache = self
            .cache_state
            .search_text_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let before = search_text_cache.len();
        search_text_cache.retain(|key, _| {
            !response_cache_scopes_include_repository(
                repository_id,
                &key.scoped_repository_ids,
                &key.freshness_scopes,
            )
        });
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchTextResponse,
            RuntimeCacheEvent::Invalidation,
            before.saturating_sub(search_text_cache.len()),
        );

        let mut search_hybrid_cache = self
            .cache_state
            .search_hybrid_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let before = search_hybrid_cache.len();
        search_hybrid_cache.retain(|key, _| {
            !response_cache_scopes_include_repository(
                repository_id,
                &key.scoped_repository_ids,
                &key.freshness_scopes,
            )
        });
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchHybridResponse,
            RuntimeCacheEvent::Invalidation,
            before.saturating_sub(search_hybrid_cache.len()),
        );

        let mut search_symbol_cache = self
            .cache_state
            .search_symbol_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let before = search_symbol_cache.len();
        search_symbol_cache.retain(|key, _| {
            !response_cache_scopes_include_repository(
                repository_id,
                &key.scoped_repository_ids,
                &key.freshness_scopes,
            )
        });
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchSymbolResponse,
            RuntimeCacheEvent::Invalidation,
            before.saturating_sub(search_symbol_cache.len()),
        );
    }

    pub(crate) fn search_pattern_type_cache_key(pattern_type: &SearchPatternType) -> &'static str {
        match pattern_type {
            SearchPatternType::Literal => "literal",
            SearchPatternType::Regex => "regex",
        }
    }

    pub(crate) fn compile_cached_safe_regex(
        &self,
        raw: &str,
    ) -> Result<regex::Regex, crate::searcher::RegexSearchError> {
        if let Some(cached) = self
            .cache_state
            .compiled_safe_regex_cache
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .get(raw)
            .cloned()
        {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::CompiledSafeRegex,
                RuntimeCacheEvent::Hit,
                1,
            );
            return Ok(cached);
        }
        self.record_runtime_cache_event(
            RuntimeCacheFamily::CompiledSafeRegex,
            RuntimeCacheEvent::Miss,
            1,
        );

        let compiled = compile_safe_regex(raw)?;
        let mut cache = self
            .cache_state
            .compiled_safe_regex_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        if let Some(cached) = cache.get(raw).cloned() {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::CompiledSafeRegex,
                RuntimeCacheEvent::Hit,
                1,
            );
            return Ok(cached);
        }
        let inserted = cache.insert(raw.to_owned(), compiled.clone()).is_none();
        if inserted {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::CompiledSafeRegex,
                RuntimeCacheEvent::Insert,
                1,
            );
        }
        self.trim_runtime_cache_to_budget(
            RuntimeCacheFamily::CompiledSafeRegex,
            &mut cache,
            |pattern, _| pattern.len().saturating_add(256),
        );
        Ok(compiled)
    }

    pub(crate) fn cached_search_text_response(
        &self,
        cache_key: &SearchTextResponseCacheKey,
    ) -> Option<CachedSearchTextResponse> {
        let cached = self
            .cache_state
            .search_text_response_cache
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .get(cache_key)
            .cloned();
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchTextResponse,
            if cached.is_some() {
                RuntimeCacheEvent::Hit
            } else {
                RuntimeCacheEvent::Miss
            },
            1,
        );
        cached
    }

    pub(crate) fn cache_search_text_response(
        &self,
        cache_key: SearchTextResponseCacheKey,
        response: &SearchTextResponse,
        source_refs: &Value,
    ) {
        let mut cache = self
            .cache_state
            .search_text_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let inserted = cache
            .insert(
                cache_key,
                CachedSearchTextResponse {
                    response: response.clone(),
                    source_refs: source_refs.clone(),
                },
            )
            .is_none();
        if inserted {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::SearchTextResponse,
                RuntimeCacheEvent::Insert,
                1,
            );
        }
        self.trim_runtime_cache_to_budget(
            RuntimeCacheFamily::SearchTextResponse,
            &mut cache,
            |_, entry| {
                serialized_value_estimated_bytes(&entry.response)
                    .saturating_add(serialized_value_estimated_bytes(&entry.source_refs))
            },
        );
    }

    pub(crate) fn cached_search_hybrid_response(
        &self,
        cache_key: &SearchHybridResponseCacheKey,
    ) -> Option<CachedSearchHybridResponse> {
        let cached = self
            .cache_state
            .search_hybrid_response_cache
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .get(cache_key)
            .cloned();
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchHybridResponse,
            if cached.is_some() {
                RuntimeCacheEvent::Hit
            } else {
                RuntimeCacheEvent::Miss
            },
            1,
        );
        cached
    }

    pub(crate) fn cache_search_hybrid_response(
        &self,
        cache_key: SearchHybridResponseCacheKey,
        response: &SearchHybridResponse,
        source_refs: &Value,
    ) {
        let mut cache = self
            .cache_state
            .search_hybrid_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let inserted = cache
            .insert(
                cache_key,
                CachedSearchHybridResponse {
                    response: response.clone(),
                    source_refs: source_refs.clone(),
                },
            )
            .is_none();
        if inserted {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::SearchHybridResponse,
                RuntimeCacheEvent::Insert,
                1,
            );
        }
        self.trim_runtime_cache_to_budget(
            RuntimeCacheFamily::SearchHybridResponse,
            &mut cache,
            |_, entry| {
                serialized_value_estimated_bytes(&entry.response)
                    .saturating_add(serialized_value_estimated_bytes(&entry.source_refs))
            },
        );
    }

    pub(crate) fn cached_search_symbol_response(
        &self,
        cache_key: &SearchSymbolResponseCacheKey,
    ) -> Option<CachedSearchSymbolResponse> {
        let cached = self
            .cache_state
            .search_symbol_response_cache
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .get(cache_key)
            .cloned();
        self.record_runtime_cache_event(
            RuntimeCacheFamily::SearchSymbolResponse,
            if cached.is_some() {
                RuntimeCacheEvent::Hit
            } else {
                RuntimeCacheEvent::Miss
            },
            1,
        );
        cached
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn cache_search_symbol_response(
        &self,
        cache_key: SearchSymbolResponseCacheKey,
        response: &SearchSymbolResponse,
        scoped_repository_ids: &[String],
        diagnostics_count: usize,
        manifest_walk_diagnostics_count: usize,
        manifest_read_diagnostics_count: usize,
        symbol_extraction_diagnostics_count: usize,
        effective_limit: usize,
    ) {
        let mut cache = self
            .cache_state
            .search_symbol_response_cache
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner());
        let inserted = cache
            .insert(
                cache_key,
                CachedSearchSymbolResponse {
                    response: response.clone(),
                    scoped_repository_ids: scoped_repository_ids.to_owned(),
                    diagnostics_count,
                    manifest_walk_diagnostics_count,
                    manifest_read_diagnostics_count,
                    symbol_extraction_diagnostics_count,
                    effective_limit,
                },
            )
            .is_none();
        if inserted {
            self.record_runtime_cache_event(
                RuntimeCacheFamily::SearchSymbolResponse,
                RuntimeCacheEvent::Insert,
                1,
            );
        }
        self.trim_runtime_cache_to_budget(
            RuntimeCacheFamily::SearchSymbolResponse,
            &mut cache,
            |_, entry| {
                serialized_value_estimated_bytes(&entry.response).saturating_add(
                    entry
                        .scoped_repository_ids
                        .iter()
                        .map(String::len)
                        .sum::<usize>(),
                )
            },
        );
    }
}