a3s-code-core 5.3.2

A3S Code Core - Embeddable AI agent library with tool execution
Documentation
use super::*;
use crate::code_intelligence::{project_layout::ProjectLayoutResolver, CodeRange, LanguageId};
use crate::workspace::{LocalWorkspaceBackend, LocalWorkspaceFile, LocalWorkspaceFileStatus};

fn snapshot(root: &Path, version: u64, paths: &[&str]) -> LocalWorkspaceManifestSnapshot {
    LocalWorkspaceManifestSnapshot {
        version,
        root: root.to_path_buf(),
        files: paths
            .iter()
            .map(|path| LocalWorkspaceFile {
                path: (*path).to_owned(),
                size: 1,
                modified_ms: Some(1),
                language: None,
                status: LocalWorkspaceFileStatus::Tracked,
                binary: false,
                generated: false,
            })
            .collect(),
        scanned_at_ms: 1,
    }
}

#[test]
fn stopped_runtime_status_is_retained_without_live_subscribers() {
    let rust_capabilities = CodeIntelligenceCapabilities {
        definition: true,
        ..CodeIntelligenceCapabilities::default()
    };
    let web_capabilities = CodeIntelligenceCapabilities {
        references: true,
        ..CodeIntelligenceCapabilities::default()
    };
    let (status, receiver) = watch::channel(CodeIntelligenceStatus {
        state: CodeIntelligenceState::Ready,
        capabilities: CodeIntelligenceCapabilities {
            definition: true,
            references: true,
            ..CodeIntelligenceCapabilities::default()
        },
        languages: vec![
            CodeIntelligenceLanguageStatus {
                language: LanguageId::from("rust"),
                state: CodeIntelligenceState::Ready,
                capabilities: rust_capabilities,
                message: None,
            },
            CodeIntelligenceLanguageStatus {
                language: LanguageId::from("typescript-javascript"),
                state: CodeIntelligenceState::Ready,
                capabilities: web_capabilities,
                message: None,
            },
        ],
        message: None,
    });
    drop(receiver);

    publish_stopped_language_status(
        &status,
        LanguageId::from("rust"),
        "process exited".to_owned(),
    );

    let current = status.borrow();
    assert_eq!(current.state, CodeIntelligenceState::Degraded);
    assert!(!current.capabilities.definition);
    assert!(current.capabilities.references);
    assert_eq!(
        current.languages[0].state,
        CodeIntelligenceState::Unavailable
    );
    assert_eq!(
        current.languages[0].message.as_deref(),
        Some("process exited")
    );
}

#[test]
fn workspace_diagnostic_aggregation_never_exceeds_the_hard_limit() {
    let diagnostic = |index| CodeDiagnostic {
        location: CodeLocation {
            path: WorkspacePath::from_normalized(format!("src/{index}.rs")),
            range: CodeRange::new(CodePosition::new(0, 0), CodePosition::new(0, 1)),
        },
        severity: None,
        code: None,
        source: Some("test".to_owned()),
        message: format!("diagnostic-{index}"),
    };
    let mut items = (0..WORKSPACE_DIAGNOSTIC_LIMIT - 1)
        .map(diagnostic)
        .collect::<Vec<_>>();

    let truncated = append_bounded(
        &mut items,
        vec![
            diagnostic(WORKSPACE_DIAGNOSTIC_LIMIT),
            diagnostic(WORKSPACE_DIAGNOSTIC_LIMIT + 1),
        ],
        WORKSPACE_DIAGNOSTIC_LIMIT,
    );

    assert!(truncated);
    assert_eq!(items.len(), WORKSPACE_DIAGNOSTIC_LIMIT);
    assert_eq!(
        items.last().unwrap().message,
        format!("diagnostic-{WORKSPACE_DIAGNOSTIC_LIMIT}")
    );
}

#[test]
fn workspace_diagnostic_selection_round_robins_languages() {
    let slots = vec![
        LanguageSlot::new(LanguageServerProfile::rust("rust"), true, DOCUMENT_CAPACITY),
        LanguageSlot::new(
            LanguageServerProfile::typescript_javascript("typescript"),
            true,
            DOCUMENT_CAPACITY,
        ),
    ];
    let paths = vec![
        WorkspacePath::from_normalized("src/a.rs"),
        WorkspacePath::from_normalized("src/b.rs"),
        WorkspacePath::from_normalized("web/main.ts"),
    ];

    let (selected, truncated) = select_workspace_diagnostic_paths(&slots, &[0, 1], &paths, 2);

    assert!(truncated);
    assert_eq!(selected[0], (0, WorkspacePath::from_normalized("src/a.rs")));
    assert_eq!(
        selected[1],
        (1, WorkspacePath::from_normalized("web/main.ts"))
    );
}

#[tokio::test]
async fn removing_the_last_supported_source_resets_failed_runtime_state() {
    let workspace = tempfile::tempdir().unwrap();
    let root = std::fs::canonicalize(workspace.path()).unwrap();
    let initial = snapshot(&root, 1, &["src/lib.rs"]);
    let file_system: Arc<dyn WorkspaceFileSystem> =
        Arc::new(LocalWorkspaceBackend::new(root.clone()));
    let runtime = WorkspaceRuntime::new(
        root.clone(),
        ProjectLayoutResolver::resolve(&initial),
        &initial,
        file_system,
        Duration::from_secs(1),
    );
    let rust = runtime
        .slots
        .iter()
        .find(|slot| slot.profile.id() == ProjectLanguageProfile::Rust)
        .unwrap();
    *rust.state.lock().await = SlotState::Failed(StartFailure {
        at: Instant::now(),
        message: "failed".to_owned(),
    });

    runtime.update_snapshot(&snapshot(&root, 2, &[])).await;

    assert!(!rust.relevant.load(Ordering::Acquire));
    assert!(matches!(*rust.state.lock().await, SlotState::Dormant));
}