a3s-code-core 5.2.7

A3S Code Core - Embeddable AI agent library with tool execution
Documentation
use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;

use crate::{
    code_intelligence::{CodePosition, NavigationKind},
    tools::{Tool, ToolContext, ToolOutput},
};

use super::{
    code_intelligence_error, format, invalid_argument, provider, query_capabilities,
    structured_success, unavailable,
};

pub(super) struct CodeNavigationTool;

#[async_trait]
impl Tool for CodeNavigationTool {
    fn name(&self) -> &str {
        "code_navigation"
    }

    fn description(&self) -> &str {
        "Navigate saved workspace code by language semantics. Resolves definitions, declarations, references, or implementations from a zero-based UTF-16 position. Returns locations only, not source code."
    }

    fn parameters(&self) -> Value {
        serde_json::json!({
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "operation": {
                    "type": "string",
                    "enum": ["definition", "declaration", "references", "implementations"]
                },
                "path": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Saved workspace document path."
                },
                "line": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 4294967295_u64,
                    "description": "Zero-based line."
                },
                "character": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 4294967295_u64,
                    "description": "Zero-based UTF-16 code-unit offset."
                }
            },
            "required": ["operation", "path", "line", "character"],
            "examples": [{
                "operation": "definition",
                "path": "src/lib.rs",
                "line": 12,
                "character": 8
            }]
        })
    }

    fn capabilities(&self, _args: &Value) -> crate::tools::ToolCapabilities {
        query_capabilities()
    }

    async fn execute(&self, args: &Value, ctx: &ToolContext) -> Result<ToolOutput> {
        let (operation, kind) = match args.get("operation").and_then(Value::as_str) {
            Some("definition") => ("definition", NavigationKind::Definition),
            Some("declaration") => ("declaration", NavigationKind::Declaration),
            Some("references") => ("references", NavigationKind::References),
            Some("implementations") => ("implementations", NavigationKind::Implementations),
            Some(_) => {
                return Ok(invalid_argument(
                    "operation must be definition, declaration, references, or implementations",
                ))
            }
            None => return Ok(invalid_argument("operation parameter is required")),
        };
        let Some(path) = args.get("path").and_then(Value::as_str) else {
            return Ok(invalid_argument("path parameter is required"));
        };
        if path.trim().is_empty() {
            return Ok(invalid_argument("path must not be empty"));
        }
        let path = match ctx.resolve_workspace_path(path) {
            Ok(path) => path,
            Err(error) => {
                return Ok(invalid_argument(format!(
                    "failed to resolve workspace path: {error}"
                )))
            }
        };
        let line = match u32_argument(args, "line") {
            Ok(value) => value,
            Err(message) => return Ok(invalid_argument(message)),
        };
        let character = match u32_argument(args, "character") {
            Ok(value) => value,
            Err(message) => return Ok(invalid_argument(message)),
        };
        let Some(provider) = provider(ctx) else {
            return Ok(unavailable(operation));
        };
        match provider
            .navigate(
                kind,
                &path,
                CodePosition::new(line, character),
                ctx.cancellation_token(),
            )
            .await
        {
            Ok(result) => Ok(structured_success(format::locations(result))),
            Err(error) => Ok(code_intelligence_error(operation, error)),
        }
    }
}

fn u32_argument(args: &Value, name: &str) -> std::result::Result<u32, String> {
    args.get(name)
        .and_then(Value::as_u64)
        .and_then(|value| u32::try_from(value).ok())
        .ok_or_else(|| format!("{name} must be an integer from 0 to 4294967295"))
}