kagi-sdk 1.0.0

Rust-first Kagi SDK with explicit official-api and session-web surfaces
Documentation
use std::fmt;

use reqwest::Method;

use crate::auth::CredentialKind;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProtocolSurface {
    OfficialApi,
    SessionWeb,
}

impl fmt::Display for ProtocolSurface {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OfficialApi => formatter.write_str("OfficialApi"),
            Self::SessionWeb => formatter.write_str("SessionWeb"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ApiVersion {
    V0,
    V1,
    NotApplicable,
}

impl fmt::Display for ApiVersion {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::V0 => formatter.write_str("v0"),
            Self::V1 => formatter.write_str("v1"),
            Self::NotApplicable => formatter.write_str("n/a"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParserShape {
    JsonEnvelope,
    Html,
    Stream,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HttpMethod {
    Get,
    Post,
}

impl HttpMethod {
    pub(crate) fn as_reqwest(self) -> Method {
        match self {
            Self::Get => Method::GET,
            Self::Post => Method::POST,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EndpointId {
    OfficialSearch,
    OfficialEnrichWeb,
    OfficialEnrichNews,
    OfficialSummarizeGet,
    OfficialSummarizePost,
    OfficialFastGpt,
    OfficialSmallwebFeed,
    SessionHtmlSearch,
    SessionSummaryLabsGet,
    SessionSummaryLabsPost,
}

impl fmt::Display for EndpointId {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.spec().name)
    }
}

#[derive(Debug, Clone, Copy)]
pub struct EndpointSpec {
    pub name: &'static str,
    pub surface: ProtocolSurface,
    pub method: HttpMethod,
    pub route: &'static str,
    pub version: ApiVersion,
    pub parser: ParserShape,
    pub allowed_credential: CredentialKind,
}

impl EndpointId {
    pub fn spec(self) -> EndpointSpec {
        match self {
            Self::OfficialSearch => EndpointSpec {
                name: "official.search",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Get,
                route: "/api/v0/search",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialEnrichWeb => EndpointSpec {
                name: "official.enrich_web",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Get,
                route: "/api/v0/enrich/web",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialEnrichNews => EndpointSpec {
                name: "official.enrich_news",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Get,
                route: "/api/v0/enrich/news",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialSummarizeGet => EndpointSpec {
                name: "official.summarize_get",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Get,
                route: "/api/v0/summarize",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialSummarizePost => EndpointSpec {
                name: "official.summarize_post",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Post,
                route: "/api/v0/summarize",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialFastGpt => EndpointSpec {
                name: "official.fastgpt",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Post,
                route: "/api/v0/fastgpt",
                version: ApiVersion::V0,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::OfficialSmallwebFeed => EndpointSpec {
                name: "official.smallweb_feed",
                surface: ProtocolSurface::OfficialApi,
                method: HttpMethod::Get,
                route: "/api/v1/smallweb/feed",
                version: ApiVersion::V1,
                parser: ParserShape::JsonEnvelope,
                allowed_credential: CredentialKind::BotToken,
            },
            Self::SessionHtmlSearch => EndpointSpec {
                name: "session.html_search",
                surface: ProtocolSurface::SessionWeb,
                method: HttpMethod::Get,
                route: "/html/search",
                version: ApiVersion::NotApplicable,
                parser: ParserShape::Html,
                allowed_credential: CredentialKind::SessionToken,
            },
            Self::SessionSummaryLabsGet => EndpointSpec {
                name: "session.summary_labs_get",
                surface: ProtocolSurface::SessionWeb,
                method: HttpMethod::Get,
                route: "/mother/summary_labs",
                version: ApiVersion::NotApplicable,
                parser: ParserShape::Stream,
                allowed_credential: CredentialKind::SessionToken,
            },
            Self::SessionSummaryLabsPost => EndpointSpec {
                name: "session.summary_labs_post",
                surface: ProtocolSurface::SessionWeb,
                method: HttpMethod::Post,
                route: "/mother/summary_labs/",
                version: ApiVersion::NotApplicable,
                parser: ParserShape::Stream,
                allowed_credential: CredentialKind::SessionToken,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{ApiVersion, EndpointId};

    #[test]
    fn official_endpoints_cover_both_v0_and_v1_routes() {
        let v0 = EndpointId::OfficialSearch.spec();
        let v1 = EndpointId::OfficialSmallwebFeed.spec();

        assert_eq!(v0.version, ApiVersion::V0);
        assert_eq!(v0.route, "/api/v0/search");
        assert_eq!(v1.version, ApiVersion::V1);
        assert_eq!(v1.route, "/api/v1/smallweb/feed");
    }
}