kaynine-core 0.1.0

Core agent loop, messages, events, policies, and provider abstractions for Kaynine
Documentation
use crate::ids::{ModelId, ProviderId};
use crate::provider::TokenMeasurementSource;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum ProviderError {
    #[error("authentication failed")]
    Auth,
    #[error("rate limited")]
    RateLimit,
    #[error("context overflow")]
    ContextOverflow,
    #[error("invalid request: {0}")]
    InvalidRequest(String),
    #[error("network error: {0}")]
    Network(String),
    #[error("protocol error: {0}")]
    Protocol(String),
    #[error("cancelled")]
    Cancelled,
    #[error("unknown provider error: {0}")]
    Unknown(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum ToolError {
    #[error("invalid arguments: {0}")]
    InvalidArguments(String),
    #[error("execution failed: {0}")]
    Execution(String),
    #[error("cancelled")]
    Cancelled,
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum CredentialError {
    #[error("credential resolve failed: {0}")]
    Resolve(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum PromptError {
    #[error("prompt render failed: {0}")]
    Render(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum ContextError {
    #[error("context load failed: {0}")]
    Load(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum StoreError {
    #[error("store io error: {0}")]
    Io(String),
    #[error("store internal error: {0}")]
    Internal(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum PolicyError {
    #[error("policy evaluation failed: {0}")]
    Evaluate(String),
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum KaynineError {
    #[error("invalid request")]
    InvalidRequest,
    #[error("session not found")]
    SessionNotFound,
    #[error("session busy")]
    SessionBusy,
    #[error("run already active")]
    RunAlreadyActive,
    #[error("revision conflict")]
    RevisionConflict,
    #[error("idempotency conflict")]
    IdempotencyConflict,
    #[error("approval expired")]
    ApprovalExpired,
    #[error("store error: {0}")]
    Store(#[from] StoreError),
    #[error("internal error")]
    Internal,
}

#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
pub enum RunFailureReason {
    #[error("agent definition unavailable")]
    DefinitionUnavailable,
    #[error("model capability mismatch: {requirement} (provider {provider}, model {model})")]
    ModelCapabilityMismatch {
        requirement: String,
        provider: ProviderId,
        model: ModelId,
    },
    #[error("context budget exceeded: {measured_tokens} > {available_tokens}")]
    ContextBudgetExceeded {
        measured_tokens: u64,
        available_tokens: u64,
        source: TokenMeasurementSource,
    },
    #[error("provider failure: {0}")]
    Provider(#[from] ProviderError),
    #[error("prompt failure: {0}")]
    Prompt(#[from] PromptError),
    #[error("context provider failure: {0}")]
    Context(#[from] ContextError),
    #[error("store failure: {0}")]
    Store(#[from] StoreError),
    #[error("internal error")]
    Internal,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn kaynine_error_roundtrips() {
        let err = KaynineError::SessionBusy;
        let json = serde_json::to_string(&err).unwrap();
        let back: KaynineError = serde_json::from_str(&json).unwrap();
        assert_eq!(back, err);
    }

    #[test]
    fn run_failure_reason_roundtrips_with_payload() {
        let reason = RunFailureReason::ContextBudgetExceeded {
            measured_tokens: 100,
            available_tokens: 50,
            source: TokenMeasurementSource::Heuristic,
        };
        let json = serde_json::to_string(&reason).unwrap();
        let back: RunFailureReason = serde_json::from_str(&json).unwrap();
        assert_eq!(back, reason);
    }

    #[test]
    fn policy_error_roundtrips() {
        let err = PolicyError::Evaluate("boom".into());
        let json = serde_json::to_string(&err).unwrap();
        let back: PolicyError = serde_json::from_str(&json).unwrap();
        assert_eq!(back, err);
    }

    #[test]
    fn provider_error_covers_spec_categories() {
        let categories = [
            ProviderError::Auth,
            ProviderError::RateLimit,
            ProviderError::ContextOverflow,
            ProviderError::InvalidRequest("x".into()),
            ProviderError::Network("x".into()),
            ProviderError::Protocol("x".into()),
            ProviderError::Cancelled,
            ProviderError::Unknown("x".into()),
        ];
        for c in categories {
            let json = serde_json::to_string(&c).unwrap();
            let back: ProviderError = serde_json::from_str(&json).unwrap();
            assert_eq!(back, c);
        }
    }
}