a3s 0.10.5

a3s — A3S coding agent CLI; `a3s code` launches the interactive TUI
Documentation
use a3s_code_core::{
    CodeDiagnostic, CodeDiagnosticSeverity, CodeIntelligenceCapabilities,
    CodeIntelligenceLanguageStatus, CodeIntelligenceState, CodeIntelligenceStatus, CodeLocation,
    CodePosition, CodeQueryResult, CodeRange, CodeSymbolKind, DocumentSnapshot, DocumentSymbol,
    SymbolInformation,
};
use serde::Serialize;

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct CodeIntelligenceStatusResponse {
    state: CodeIntelligenceStateDto,
    capabilities: CodeIntelligenceCapabilitiesDto,
    languages: Vec<CodeIntelligenceLanguageStatusDto>,
    message: Option<String>,
}

impl From<CodeIntelligenceStatus> for CodeIntelligenceStatusResponse {
    fn from(status: CodeIntelligenceStatus) -> Self {
        Self {
            state: status.state.into(),
            capabilities: status.capabilities.into(),
            languages: status.languages.into_iter().map(Into::into).collect(),
            message: status.message,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CodeIntelligenceLanguageStatusDto {
    language: String,
    state: CodeIntelligenceStateDto,
    capabilities: CodeIntelligenceCapabilitiesDto,
    message: Option<String>,
}

impl From<CodeIntelligenceLanguageStatus> for CodeIntelligenceLanguageStatusDto {
    fn from(status: CodeIntelligenceLanguageStatus) -> Self {
        Self {
            language: status.language.to_string(),
            state: status.state.into(),
            capabilities: status.capabilities.into(),
            message: status.message,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CodeIntelligenceCapabilitiesDto {
    document_symbols: bool,
    workspace_symbols: bool,
    definition: bool,
    declaration: bool,
    references: bool,
    implementations: bool,
    diagnostics: bool,
}

impl From<CodeIntelligenceCapabilities> for CodeIntelligenceCapabilitiesDto {
    fn from(capabilities: CodeIntelligenceCapabilities) -> Self {
        Self {
            document_symbols: capabilities.document_symbols,
            workspace_symbols: capabilities.workspace_symbols,
            definition: capabilities.definition,
            declaration: capabilities.declaration,
            references: capabilities.references,
            implementations: capabilities.implementations,
            diagnostics: capabilities.diagnostics,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
enum CodeIntelligenceStateDto {
    Starting,
    Ready,
    Degraded,
    Unavailable,
}

impl From<CodeIntelligenceState> for CodeIntelligenceStateDto {
    fn from(state: CodeIntelligenceState) -> Self {
        match state {
            CodeIntelligenceState::Starting => Self::Starting,
            CodeIntelligenceState::Ready => Self::Ready,
            CodeIntelligenceState::Degraded => Self::Degraded,
            CodeIntelligenceState::Unavailable => Self::Unavailable,
        }
    }
}

pub(in crate::api::code_web) type DocumentSymbolResponse = QueryResponse<DocumentSymbolDto>;
pub(in crate::api::code_web) type WorkspaceSymbolResponse = QueryResponse<SymbolInformationDto>;
pub(in crate::api::code_web) type NavigationResponse = QueryResponse<CodeLocationDto>;
pub(in crate::api::code_web) type DiagnosticResponse = QueryResponse<CodeDiagnosticDto>;

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct QueryResponse<T> {
    items: Vec<T>,
    truncated: bool,
    workspace_revision: u64,
    document: Option<DocumentSnapshotDto>,
}

impl<T, U> From<CodeQueryResult<T>> for QueryResponse<U>
where
    U: From<T>,
{
    fn from(result: CodeQueryResult<T>) -> Self {
        Self {
            items: result.items.into_iter().map(Into::into).collect(),
            truncated: result.truncated,
            workspace_revision: result.workspace_revision,
            document: result.document.map(Into::into),
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct DocumentSnapshotDto {
    revision: u64,
    content_hash: String,
    stale: bool,
}

impl From<DocumentSnapshot> for DocumentSnapshotDto {
    fn from(snapshot: DocumentSnapshot) -> Self {
        Self {
            revision: snapshot.revision.value(),
            content_hash: snapshot.content_hash,
            stale: snapshot.stale,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct DocumentSymbolDto {
    name: String,
    detail: Option<String>,
    kind: CodeSymbolKindDto,
    range: CodeRangeDto,
    selection_range: CodeRangeDto,
    children: Vec<DocumentSymbolDto>,
}

impl From<DocumentSymbol> for DocumentSymbolDto {
    fn from(symbol: DocumentSymbol) -> Self {
        Self {
            name: symbol.name,
            detail: symbol.detail,
            kind: symbol.kind.into(),
            range: symbol.range.into(),
            selection_range: symbol.selection_range.into(),
            children: symbol.children.into_iter().map(Into::into).collect(),
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct SymbolInformationDto {
    name: String,
    kind: CodeSymbolKindDto,
    location: CodeLocationDto,
    container_name: Option<String>,
}

impl From<SymbolInformation> for SymbolInformationDto {
    fn from(symbol: SymbolInformation) -> Self {
        Self {
            name: symbol.name,
            kind: symbol.kind.into(),
            location: symbol.location.into(),
            container_name: symbol.container_name,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct CodeDiagnosticDto {
    location: CodeLocationDto,
    severity: Option<CodeDiagnosticSeverityDto>,
    code: Option<String>,
    source: Option<String>,
    message: String,
}

impl From<CodeDiagnostic> for CodeDiagnosticDto {
    fn from(diagnostic: CodeDiagnostic) -> Self {
        Self {
            location: diagnostic.location.into(),
            severity: diagnostic.severity.map(Into::into),
            code: diagnostic.code,
            source: diagnostic.source,
            message: diagnostic.message,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
enum CodeDiagnosticSeverityDto {
    Error,
    Warning,
    Information,
    Hint,
}

impl From<CodeDiagnosticSeverity> for CodeDiagnosticSeverityDto {
    fn from(severity: CodeDiagnosticSeverity) -> Self {
        match severity {
            CodeDiagnosticSeverity::Error => Self::Error,
            CodeDiagnosticSeverity::Warning => Self::Warning,
            CodeDiagnosticSeverity::Information => Self::Information,
            CodeDiagnosticSeverity::Hint => Self::Hint,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(in crate::api::code_web) struct CodeLocationDto {
    path: String,
    range: CodeRangeDto,
}

impl From<CodeLocation> for CodeLocationDto {
    fn from(location: CodeLocation) -> Self {
        Self {
            path: location.path.as_str().to_owned(),
            range: location.range.into(),
        }
    }
}

#[derive(Debug, Serialize)]
struct CodeRangeDto {
    start: CodePositionDto,
    end: CodePositionDto,
}

impl From<CodeRange> for CodeRangeDto {
    fn from(range: CodeRange) -> Self {
        Self {
            start: range.start.into(),
            end: range.end.into(),
        }
    }
}

#[derive(Debug, Serialize)]
struct CodePositionDto {
    line: u32,
    character: u32,
}

impl From<CodePosition> for CodePositionDto {
    fn from(position: CodePosition) -> Self {
        Self {
            line: position.line,
            character: position.character,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
enum CodeSymbolKindDto {
    File,
    Module,
    Namespace,
    Package,
    Class,
    Method,
    Property,
    Field,
    Constructor,
    Enum,
    Interface,
    Function,
    Variable,
    Constant,
    String,
    Number,
    Boolean,
    Array,
    Object,
    Key,
    Null,
    EnumMember,
    Struct,
    Event,
    Operator,
    TypeParameter,
    Unknown,
}

impl From<CodeSymbolKind> for CodeSymbolKindDto {
    fn from(kind: CodeSymbolKind) -> Self {
        match kind {
            CodeSymbolKind::File => Self::File,
            CodeSymbolKind::Module => Self::Module,
            CodeSymbolKind::Namespace => Self::Namespace,
            CodeSymbolKind::Package => Self::Package,
            CodeSymbolKind::Class => Self::Class,
            CodeSymbolKind::Method => Self::Method,
            CodeSymbolKind::Property => Self::Property,
            CodeSymbolKind::Field => Self::Field,
            CodeSymbolKind::Constructor => Self::Constructor,
            CodeSymbolKind::Enum => Self::Enum,
            CodeSymbolKind::Interface => Self::Interface,
            CodeSymbolKind::Function => Self::Function,
            CodeSymbolKind::Variable => Self::Variable,
            CodeSymbolKind::Constant => Self::Constant,
            CodeSymbolKind::String => Self::String,
            CodeSymbolKind::Number => Self::Number,
            CodeSymbolKind::Boolean => Self::Boolean,
            CodeSymbolKind::Array => Self::Array,
            CodeSymbolKind::Object => Self::Object,
            CodeSymbolKind::Key => Self::Key,
            CodeSymbolKind::Null => Self::Null,
            CodeSymbolKind::EnumMember => Self::EnumMember,
            CodeSymbolKind::Struct => Self::Struct,
            CodeSymbolKind::Event => Self::Event,
            CodeSymbolKind::Operator => Self::Operator,
            CodeSymbolKind::TypeParameter => Self::TypeParameter,
            _ => Self::Unknown,
        }
    }
}

#[cfg(test)]
mod tests {
    use a3s_code_core::{
        CodePosition, CodeRange, CodeSymbolKind, DocumentRevision, DocumentSnapshot, DocumentSymbol,
    };

    use super::*;

    #[test]
    fn query_response_preserves_saved_document_evidence() {
        let result = CodeQueryResult {
            items: vec![DocumentSymbol {
                name: "main".to_owned(),
                detail: None,
                kind: CodeSymbolKind::Function,
                range: CodeRange::new(CodePosition::new(1, 0), CodePosition::new(3, 1)),
                selection_range: CodeRange::new(CodePosition::new(1, 3), CodePosition::new(1, 7)),
                children: Vec::new(),
            }],
            truncated: true,
            workspace_revision: 17,
            document: Some(DocumentSnapshot {
                revision: DocumentRevision::new(4),
                content_hash: "hash".to_owned(),
                stale: false,
            }),
        };

        let value =
            serde_json::to_value(DocumentSymbolResponse::from(result)).expect("serialize response");

        assert_eq!(value["workspaceRevision"], 17);
        assert_eq!(value["truncated"], true);
        assert_eq!(value["document"]["revision"], 4);
        assert_eq!(value["document"]["contentHash"], "hash");
        assert_eq!(value["items"][0]["kind"], "function");
        assert_eq!(value["items"][0]["selectionRange"]["start"]["character"], 3);
    }
}