rustglm 1.0.0

Production-oriented async Rust SDK for Zhipu GLM, Realtime, Batch, RAG, and MCP
Documentation
use reqwest::StatusCode;
use thiserror::Error;

pub type Result<T> = std::result::Result<T, SdkError>;

#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("HTTP {status}: {message}")]
pub struct ApiError {
    pub status: StatusCode,
    pub code: Option<String>,
    pub message: String,
    pub request_id: Option<String>,
    pub body: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ConfigurationError {
    #[error("invalid value for {field}: {reason}")]
    InvalidValue { field: &'static str, reason: String },
    #[error("{0}")]
    Message(String),
}

impl From<String> for ConfigurationError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for ConfigurationError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ValidationError {
    #[error("missing required field {0}")]
    MissingField(&'static str),
    #[error("invalid value for {field}: {reason}")]
    InvalidValue { field: &'static str, reason: String },
    #[error("serialization failed: {0}")]
    Serialization(String),
    #[error("{0}")]
    Message(String),
}

impl ValidationError {
    pub fn contains(&self, pattern: &str) -> bool {
        self.to_string().contains(pattern)
    }
}

impl From<String> for ValidationError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for ValidationError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum TimeoutError {
    #[error("{operation} timed out")]
    Operation { operation: &'static str },
    #[error("{0}")]
    Message(String),
}

impl From<String> for TimeoutError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for TimeoutError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum StreamError {
    #[error("stream payload could not be decoded: {0}")]
    Decode(String),
    #[error("stream protocol violation: {0}")]
    Protocol(String),
    #[error("stream channel is closed")]
    Closed,
    #[error("{0}")]
    Message(String),
}

impl From<String> for StreamError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for StreamError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum UnsupportedError {
    #[error("capability {0} is not supported")]
    Capability(String),
    #[error("{0}")]
    Message(String),
}

impl From<String> for UnsupportedError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for UnsupportedError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum AgentError {
    #[error("agent response contained no choices")]
    EmptyResponse,
    #[error("agent stopped after {steps} steps")]
    StepLimit { steps: usize },
    #[error("{0}")]
    Message(String),
}

impl From<String> for AgentError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for AgentError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ToolError {
    #[error("tool {0} is not registered")]
    NotRegistered(String),
    #[error("invalid arguments for tool {tool}: {reason}")]
    InvalidArguments { tool: String, reason: String },
    #[error("tool {tool} failed: {reason}")]
    Execution { tool: String, reason: String },
    #[error("{0}")]
    Message(String),
}

impl From<String> for ToolError {
    fn from(value: String) -> Self {
        Self::Message(value)
    }
}

impl From<&str> for ToolError {
    fn from(value: &str) -> Self {
        Self::Message(value.to_owned())
    }
}

#[cfg(feature = "batch")]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum BatchError {
    #[error("batch input_file_id and endpoint are required")]
    MissingCreateFields,
    #[error("invalid batch list limit {0}; expected 1..=100")]
    InvalidLimit(u32),
}

#[cfg(feature = "rag")]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum RagError {
    #[error("knowledge base name cannot be empty")]
    EmptyKnowledgeName,
    #[error("knowledge retrieval requires at least one knowledge base")]
    EmptyKnowledgeIds,
    #[error("invalid RAG pagination: page must be positive and size must be 1..=100")]
    InvalidPagination,
    #[error("invalid RAG field {field}: {reason}")]
    InvalidField { field: &'static str, reason: String },
}

#[cfg(feature = "mcp")]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum McpClientError {
    #[error("invalid MCP endpoint: {0}")]
    InvalidEndpoint(String),
    #[error("invalid MCP header {0}")]
    InvalidHeader(String),
    #[error("failed to build MCP HTTP client: {0}")]
    ClientBuild(String),
    #[error("MCP initialization failed: {0}")]
    Initialize(String),
    #[error("MCP request failed: {0}")]
    Request(String),
    #[error("MCP shutdown failed: {0}")]
    Shutdown(String),
}

#[derive(Debug, Error)]
pub enum SdkError {
    #[error("invalid configuration: {0}")]
    Configuration(#[from] ConfigurationError),
    #[error("invalid request: {0}")]
    Validation(#[from] ValidationError),
    #[error("transport error: {0}")]
    Transport(#[from] reqwest::Error),
    #[error("operation timed out: {0}")]
    Timeout(#[from] TimeoutError),
    #[error(transparent)]
    Api(#[from] ApiError),
    #[error("response decode error: {message}")]
    Decode { message: String, body: String },
    #[error("stream error: {0}")]
    Stream(#[from] StreamError),
    #[cfg(feature = "realtime")]
    #[error("WebSocket error: {0}")]
    WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
    #[error("unsupported capability: {0}")]
    Unsupported(#[from] UnsupportedError),
    #[error("agent error: {0}")]
    Agent(#[from] AgentError),
    #[error("tool execution error: {0}")]
    Tool(#[from] ToolError),
    #[cfg(feature = "batch")]
    #[error(transparent)]
    Batch(#[from] BatchError),
    #[cfg(feature = "rag")]
    #[error(transparent)]
    Rag(#[from] RagError),
    #[cfg(feature = "mcp")]
    #[error(transparent)]
    Mcp(#[from] McpClientError),
}