agentix 0.16.1

Multi-provider LLM client for Rust — streaming, non-streaming, tool calls, MCP, DeepSeek, OpenAI, Anthropic, Gemini
Documentation
//! Shared types used across the raw provider and request layers.
//!
//! These types are kept separate to avoid circular dependencies between
//! `raw/` (provider wire formats) and `request` (public API).

// ── Usage & Accounting ────────────────────────────────────────────────────────

/// Token usage statistics for a single request or an entire session.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageStats {
    /// Number of tokens in the prompt.
    pub prompt_tokens: usize,
    /// Number of tokens generated by the model.
    pub completion_tokens: usize,
    /// Total tokens used (prompt + completion).
    pub total_tokens: usize,
    /// Tokens read from the provider's prompt cache (Anthropic: cache_read_input_tokens).
    pub cache_read_tokens: usize,
    /// Tokens written into the provider's prompt cache (Anthropic: cache_creation_input_tokens).
    pub cache_creation_tokens: usize,
}

impl std::ops::AddAssign for UsageStats {
    fn add_assign(&mut self, rhs: Self) {
        self.prompt_tokens += rhs.prompt_tokens;
        self.completion_tokens += rhs.completion_tokens;
        self.total_tokens += rhs.total_tokens;
        self.cache_read_tokens += rhs.cache_read_tokens;
        self.cache_creation_tokens += rhs.cache_creation_tokens;
    }
}

// ── Finish reason ─────────────────────────────────────────────────────────────

/// Why the model stopped generating.
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
    /// Natural end of the response.
    #[default]
    Stop,
    /// Hit the `max_tokens` limit — response may be truncated.
    Length,
    /// The model emitted one or more tool calls.
    ToolCalls,
    /// Content was filtered by the provider's safety system.
    ContentFilter,
    /// Any other provider-specific reason not covered above.
    Other(String),
}

impl FinishReason {
    /// Returns `true` if the response was truncated due to token limit.
    pub fn is_truncated(&self) -> bool {
        matches!(self, FinishReason::Length)
    }
}


impl From<&str> for FinishReason {
    fn from(s: &str) -> Self {
        match s {
            // OpenAI / Gemini
            "stop" | "STOP" => FinishReason::Stop,
            "length" | "MAX_TOKENS" => FinishReason::Length,
            "tool_calls" | "MALFORMED_FUNCTION_CALL" => FinishReason::ToolCalls,
            "content_filter" | "SAFETY" | "PROHIBITED_CONTENT" | "SPII" | "BLOCKLIST" => FinishReason::ContentFilter,
            // Anthropic
            "end_turn" => FinishReason::Stop,
            "max_tokens" => FinishReason::Length,
            "tool_use" => FinishReason::ToolCalls,
            "stop_sequence" => FinishReason::Stop,
            other => FinishReason::Other(other.to_string()),
        }
    }
}
/// The result of a non-streaming (complete) API call.
#[derive(Debug, Clone, Default)]
pub struct CompleteResponse {
    /// The text content produced by the model (may be empty if only tool calls).
    pub content: Option<String>,
    /// Chain-of-thought / reasoning text, if any.
    pub reasoning: Option<String>,
    /// Tool calls requested by the model.
    pub tool_calls: Vec<crate::request::ToolCall>,
    /// Token usage statistics.
    pub usage: UsageStats,
    /// Why the model stopped generating.
    pub finish_reason: FinishReason,
}

impl CompleteResponse {
    /// Deserialize the response content as JSON into `T`.
    ///
    /// Equivalent to `serde_json::from_str(response.content.unwrap_or_default())`.
    /// Useful with [`Request::json_schema`] or [`Request::json`].
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> serde_json::Result<T> {
        serde_json::from_str(self.content.as_deref().unwrap_or(""))
    }
}

// ── Internal provider events ──────────────────────────────────────────────────

/// A tool call fragment emitted during a streaming turn.
#[derive(Debug, Clone)]
pub struct ToolCallChunk {
    /// Unique call ID assigned by the provider.
    pub id: String,
    /// Tool name being invoked.
    pub name: String,
    /// Incremental JSON argument fragment.
    pub delta: String,
    /// Zero-based index when multiple tool calls happen in one turn.
    pub index: u32,
}

// ── Streaming accumulator ─────────────────────────────────────────────────────

/// Accumulates a single tool-call's incremental SSE deltas until the stream ends.
#[derive(Debug)]
pub struct PartialToolCall {
    /// Unique call ID assigned by the provider.
    pub id: String,
    /// Tool name being invoked.
    pub name: String,
    /// JSON arguments accumulated so far.
    pub arguments: String,
}

/// Provider-agnostic streaming state — accumulates text, reasoning, and
/// tool-call fragments across SSE chunks.
pub struct StreamBufs {
    /// Accumulated text content.
    pub content_buf: String,
    /// Accumulated reasoning / chain-of-thought.
    pub reasoning_buf: String,
    /// Sparse per-index partial tool-call buffers.
    pub tool_call_bufs: Vec<Option<PartialToolCall>>,
}

impl StreamBufs {
    pub fn new() -> Self {
        Self {
            content_buf: String::new(),
            reasoning_buf: String::new(),
            tool_call_bufs: Vec::new(),
        }
    }
}

impl Default for StreamBufs {
    fn default() -> Self {
        Self::new()
    }
}