ai_lib/types/
response.rs

1use crate::types::Choice;
2use serde::{Deserialize, Serialize};
3
4/// Usage token counts returned by providers as part of chat response.
5///
6/// Semantically this is response metadata (per-request usage info) and therefore
7/// lives in the `types::response` module. For convenience this type is also
8/// re-exported at the crate root as `ai_lib::Usage`.
9#[doc(alias = "token_usage")]
10#[doc(alias = "usage_info")]
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Usage {
13    pub prompt_tokens: u32,
14    pub completion_tokens: u32,
15    pub total_tokens: u32,
16}
17
18/// Indicates the reliability and source of usage data
19#[doc(alias = "usage_status")]
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub enum UsageStatus {
22    /// Usage data is accurate and finalized from the provider
23    #[serde(rename = "finalized")]
24    Finalized,
25    /// Usage data is estimated (e.g., using tokenizer approximation)
26    #[serde(rename = "estimated")]
27    Estimated,
28    /// Usage data is not yet available (e.g., streaming in progress)
29    #[serde(rename = "pending")]
30    Pending,
31    /// Provider doesn't support usage tracking
32    #[serde(rename = "unsupported")]
33    Unsupported,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ChatCompletionResponse {
38    pub id: String,
39    pub object: String,
40    pub created: u64,
41    pub model: String,
42    pub choices: Vec<Choice>,
43    pub usage: Usage,
44    /// Indicates the reliability and source of usage data
45    pub usage_status: UsageStatus,
46}
47
48impl ChatCompletionResponse {
49    /// Get the first textual content from the first choice.
50    /// Returns an error if choices are empty or content is non-text.
51    pub fn first_text(&self) -> Result<&str, crate::types::AiLibError> {
52        let choice = self.choices.first().ok_or_else(|| {
53            crate::types::AiLibError::InvalidModelResponse("empty choices".into())
54        })?;
55        match &choice.message.content {
56            crate::types::common::Content::Text(t) => Ok(t.as_str()),
57            other => Err(crate::types::AiLibError::InvalidModelResponse(format!(
58                "expected text content, got {:?}",
59                other
60            ))),
61        }
62    }
63}