Codex Handbook
app-server/src/filters.rs 158 lines
use codex_app_server_protocol::ThreadSourceKind;use codex_core::INTERACTIVE_SESSION_SOURCES;use codex_protocol::protocol::SessionSource as CoreSessionSource;use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;pub(crate) fn compute_source_filters(    source_kinds: Option<Vec<ThreadSourceKind>>,) -> (Vec<CoreSessionSource>, Option<Vec<ThreadSourceKind>>) {    let Some(source_kinds) = source_kinds else {        return (INTERACTIVE_SESSION_SOURCES.to_vec(), None);    };    if source_kinds.is_empty() {        return (INTERACTIVE_SESSION_SOURCES.to_vec(), None);    }    let requires_post_filter = source_kinds.iter().any(|kind| {        matches!(            kind,            ThreadSourceKind::Exec                | ThreadSourceKind::AppServer                | ThreadSourceKind::SubAgent                | ThreadSourceKind::SubAgentReview                | ThreadSourceKind::SubAgentCompact                | ThreadSourceKind::SubAgentThreadSpawn                | ThreadSourceKind::SubAgentOther                | ThreadSourceKind::Unknown        )    });    if requires_post_filter {        (Vec::new(), Some(source_kinds))    } else {        let interactive_sources = source_kinds            .iter()            .filter_map(|kind| match kind {                ThreadSourceKind::Cli => Some(CoreSessionSource::Cli),                ThreadSourceKind::VsCode => Some(CoreSessionSource::VSCode),                ThreadSourceKind::Exec                | ThreadSourceKind::AppServer                | ThreadSourceKind::SubAgent                | ThreadSourceKind::SubAgentReview                | ThreadSourceKind::SubAgentCompact                | ThreadSourceKind::SubAgentThreadSpawn                | ThreadSourceKind::SubAgentOther                | ThreadSourceKind::Unknown => None,            })            .collect::<Vec<_>>();        (interactive_sources, Some(source_kinds))    }}pub(crate) fn source_kind_matches(source: &CoreSessionSource, filter: &[ThreadSourceKind]) -> bool {    filter.iter().any(|kind| match kind {        ThreadSourceKind::Cli => matches!(source, CoreSessionSource::Cli),        ThreadSourceKind::VsCode => matches!(source, CoreSessionSource::VSCode),        ThreadSourceKind::Exec => matches!(source, CoreSessionSource::Exec),        ThreadSourceKind::AppServer => matches!(source, CoreSessionSource::Mcp),        ThreadSourceKind::SubAgent => matches!(source, CoreSessionSource::SubAgent(_)),        ThreadSourceKind::SubAgentReview => {            matches!(                source,                CoreSessionSource::SubAgent(CoreSubAgentSource::Review)            )        }        ThreadSourceKind::SubAgentCompact => {            matches!(                source,                CoreSessionSource::SubAgent(CoreSubAgentSource::Compact)            )        }        ThreadSourceKind::SubAgentThreadSpawn => matches!(            source,            CoreSessionSource::SubAgent(CoreSubAgentSource::ThreadSpawn { .. })        ),        ThreadSourceKind::SubAgentOther => matches!(            source,            CoreSessionSource::SubAgent(CoreSubAgentSource::Other(_))        ),        ThreadSourceKind::Unknown => matches!(source, CoreSessionSource::Unknown),    })}#[cfg(test)]mod tests {    use super::*;    use codex_protocol::ThreadId;    use pretty_assertions::assert_eq;    use uuid::Uuid;    #[test]    fn compute_source_filters_defaults_to_interactive_sources() {        let (allowed_sources, filter) = compute_source_filters(/*source_kinds*/ None);        assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec());        assert_eq!(filter, None);    }    #[test]    fn compute_source_filters_empty_means_interactive_sources() {        let (allowed_sources, filter) = compute_source_filters(Some(Vec::new()));        assert_eq!(allowed_sources, INTERACTIVE_SESSION_SOURCES.to_vec());        assert_eq!(filter, None);    }    #[test]    fn compute_source_filters_interactive_only_skips_post_filtering() {        let source_kinds = vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode];        let (allowed_sources, filter) = compute_source_filters(Some(source_kinds.clone()));        assert_eq!(            allowed_sources,            vec![CoreSessionSource::Cli, CoreSessionSource::VSCode]        );        assert_eq!(filter, Some(source_kinds));    }    #[test]    fn compute_source_filters_subagent_variant_requires_post_filtering() {        let source_kinds = vec![ThreadSourceKind::SubAgentReview];        let (allowed_sources, filter) = compute_source_filters(Some(source_kinds.clone()));        assert_eq!(allowed_sources, Vec::new());        assert_eq!(filter, Some(source_kinds));    }    #[test]    fn source_kind_matches_distinguishes_subagent_variants() {        let parent_thread_id =            ThreadId::from_string(&Uuid::new_v4().to_string()).expect("valid thread id");        let review = CoreSessionSource::SubAgent(CoreSubAgentSource::Review);        let spawn = CoreSessionSource::SubAgent(CoreSubAgentSource::ThreadSpawn {            parent_thread_id,            depth: 1,            agent_path: None,            agent_nickname: None,            agent_role: None,        });        assert!(source_kind_matches(            &review,            &[ThreadSourceKind::SubAgentReview]        ));        assert!(!source_kind_matches(            &review,            &[ThreadSourceKind::SubAgentThreadSpawn]        ));        assert!(source_kind_matches(            &spawn,            &[ThreadSourceKind::SubAgentThreadSpawn]        ));        assert!(!source_kind_matches(            &spawn,            &[ThreadSourceKind::SubAgentReview]        ));    }}