magi-code 0.61.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::{
    ToolResult, ToolResultDisplay, ToolRuntime,
    args::{DiagnosticsArgs, ReferencesArgs},
    contract::{metadata_key as meta, tool_name},
    fs::ExistingPathPolicy,
};
use crate::{agent::cancellation::AgentCancellation, output::redact_sensitive_text};
use serde_json::json;

const LSP_DISABLED_DIAGNOSTICS: &str =
    "LSP diagnostics unavailable: set lsp.enabled=true and configure a language server";
const LSP_DISABLED_REFERENCES: &str =
    "LSP references unavailable: set lsp.enabled=true and configure a language server";

impl ToolRuntime {
    pub(super) fn diagnostics(
        &self,
        args: DiagnosticsArgs,
        cancellation: &AgentCancellation,
    ) -> anyhow::Result<ToolResult> {
        let limit = args.limit();
        let path = args
            .path
            .as_deref()
            .map(|path| self.resolve_existing_path(path, ExistingPathPolicy::lsp()))
            .transpose()?;
        cancellation.check()?;
        let Some(manager) = self.lsp_manager() else {
            return Ok(lsp_tool_result(
                tool_name::DIAGNOSTICS,
                LSP_DISABLED_DIAGNOSTICS.to_string(),
                json!({
                    (meta::LSP_ENABLED): false,
                    (meta::LIMIT): limit,
                }),
            ));
        };
        manager.shutdown_idle();
        let content = manager
            .diagnostics_tool(path.as_deref(), limit, cancellation)
            .map_err(redact_lsp_error)?;
        manager.shutdown_idle();
        Ok(lsp_tool_result(
            tool_name::DIAGNOSTICS,
            content.clone(),
            json!({
                (meta::LSP_ENABLED): true,
                (meta::PATH): path.as_ref().map(|path| path.display().to_string()),
                (meta::LIMIT): limit,
                (meta::DIAGNOSTIC_COUNT): content.lines().filter(|line| line.starts_with("- ")).count(),
            }),
        ))
    }

    pub(super) fn references(
        &self,
        args: ReferencesArgs,
        cancellation: &AgentCancellation,
    ) -> anyhow::Result<ToolResult> {
        let limit = args.limit();
        let path = self.resolve_existing_path(&args.path, ExistingPathPolicy::lsp())?;
        cancellation.check()?;
        let Some(manager) = self.lsp_manager() else {
            return Ok(lsp_tool_result(
                tool_name::REFERENCES,
                LSP_DISABLED_REFERENCES.to_string(),
                json!({
                    (meta::LSP_ENABLED): false,
                    (meta::PATH): path.display().to_string(),
                    (meta::LIMIT): limit,
                }),
            ));
        };
        let content = manager
            .references_tool(
                &path,
                args.line,
                args.column,
                args.include_declaration,
                limit,
                cancellation,
            )
            .map_err(redact_lsp_error)?;
        manager.shutdown_idle();
        Ok(lsp_tool_result(
            tool_name::REFERENCES,
            content.clone(),
            json!({
                (meta::LSP_ENABLED): true,
                (meta::PATH): path.display().to_string(),
                (meta::LIMIT): limit,
                (meta::REFERENCES): count_reference_lines(&content),
            }),
        ))
    }
}

fn lsp_tool_result(tool_name: &str, content: String, metadata: serde_json::Value) -> ToolResult {
    ToolResult {
        tool_name: tool_name.to_string(),
        success: true,
        content,
        metadata,
        display: ToolResultDisplay::default(),
    }
}

fn redact_lsp_error(error: anyhow::Error) -> anyhow::Error {
    anyhow::anyhow!(redact_sensitive_text(&error.to_string()))
}

fn count_reference_lines(content: &str) -> usize {
    content
        .lines()
        .filter(|line| !line.trim().is_empty() && !line.starts_with("... truncated"))
        .count()
}

#[cfg(test)]
mod tests {
    use crate::{
        config::{LspServerConfig, LspSettings, McPaths, Settings},
        tools::ToolRuntime,
    };
    use serde_json::json;
    use std::fs;

    #[test]
    fn diagnostics_and_references_dispatch_return_disabled_guidance() {
        let temp = tempfile::TempDir::new().unwrap();
        let path = temp.path().join("lib.rs");
        fs::write(&path, "fn main() {}\n").unwrap();
        let runtime = ToolRuntime::new(temp.path()).unwrap();

        let diagnostics = runtime.dispatch("diagnostics", json!({"path":"lib.rs"}));
        assert!(diagnostics.success);
        assert_eq!(diagnostics.tool_name, "diagnostics");
        assert!(diagnostics.content.contains("lsp.enabled=true"));

        let references =
            runtime.dispatch("references", json!({"path":"lib.rs","line":1,"column":1}));
        assert!(references.success);
        assert_eq!(references.tool_name, "references");
        assert!(references.content.contains("lsp.enabled=true"));
    }

    #[test]
    fn diagnostics_and_references_reject_paths_outside_cwd() {
        let root = tempfile::TempDir::new().unwrap();
        let outside = tempfile::NamedTempFile::new().unwrap();
        let runtime = ToolRuntime::new(root.path()).unwrap();

        let diagnostics = runtime.dispatch("diagnostics", json!({"path": outside.path()}));
        assert!(!diagnostics.success);
        assert!(diagnostics.content.contains("escapes cwd"));

        let references = runtime.dispatch(
            "references",
            json!({"path": outside.path(), "line": 1, "column": 1}),
        );
        assert!(!references.success);
        assert!(references.content.contains("escapes cwd"));
    }

    fn lsp_error_server_script() -> String {
        r#"
import json, sys

def read_msg():
    header = b''
    while not header.endswith(b'\r\n\r\n'):
        chunk = sys.stdin.buffer.readline()
        if not chunk:
            return None
        header += chunk
    length = 0
    for line in header.decode().splitlines():
        if line.lower().startswith('content-length:'):
            length = int(line.split(':', 1)[1].strip())
    return json.loads(sys.stdin.buffer.read(length).decode())

def send(value):
    body = json.dumps(value, separators=(',', ':')).encode()
    sys.stdout.buffer.write(b'Content-Length: ' + str(len(body)).encode() + b'\r\n\r\n' + body)
    sys.stdout.buffer.flush()

while True:
    msg = read_msg()
    if msg is None:
        break
    method = msg.get('method')
    if method == 'initialize':
        send({'jsonrpc':'2.0','id':msg['id'],'result':{'capabilities':{'textDocumentSync':1,'referencesProvider':True}}})
    elif method in ('initialized','textDocument/didOpen','textDocument/didChange'):
        pass
    elif method == 'textDocument/references':
        send({'jsonrpc':'2.0','id':msg['id'],'error':{'code':-32000,'message':'api_key=' + 'sk-' + ('x' * 24) + ' token=' + ('x' * 16)}})
    elif method == 'shutdown':
        send({'jsonrpc':'2.0','id':msg['id'],'result':None})
    elif method == 'exit':
        break
"#
        .to_string()
    }

    #[test]
    fn lsp_manager_errors_are_redacted_before_tool_result_content() {
        let temp = tempfile::TempDir::new().unwrap();
        let path = temp.path().join("lib.rs");
        fs::write(&path, "fn main() {}\n").unwrap();
        let api_key = format!("sk-{}", "x".repeat(24));
        let token = "x".repeat(16);
        let mut settings = Settings {
            lsp: LspSettings {
                enabled: true,
                diagnostics_wait_ms: 1_000,
                ..LspSettings::default()
            },
            ..Settings::default()
        };
        settings.lsp.servers.insert(
            "rust-analyzer".to_string(),
            LspServerConfig {
                command: "python3".to_string(),
                args: vec![
                    "-u".to_string(),
                    "-c".to_string(),
                    lsp_error_server_script(),
                ],
                enabled: true,
            },
        );
        let runtime = ToolRuntime::new_with_full_settings_and_mcp(
            temp.path(),
            McPaths::from_root(temp.path().join("mc-home")),
            settings,
            None,
        )
        .unwrap();

        let result = runtime.dispatch("references", json!({"path":"lib.rs","line":1,"column":1}));

        assert!(!result.success);
        assert!(result.content.contains("<redacted>"), "{}", result.content);
        assert!(!result.content.contains(&api_key));
        assert!(!result.content.contains(&token));
    }
}