Skip to main content

cpex_core/extensions/
completion.rs

1// Location: ./crates/cpex-core/src/extensions/completion.rs
2// Copyright 2025
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Teryl Taylor
5//
6// CompletionExtension — LLM completion information.
7// Mirrors cpex/framework/extensions/completion.py.
8
9use serde::{Deserialize, Serialize};
10
11/// Why the model stopped generating.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum StopReason {
15    /// Natural end of message.
16    End,
17    /// Complete response (Harmony format).
18    Return,
19    /// Tool/function invocation.
20    Call,
21    /// Hit token limit.
22    MaxTokens,
23    /// Hit custom stop sequence.
24    StopSequence,
25}
26
27/// Token usage statistics.
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct TokenUsage {
30    /// Input tokens consumed.
31    #[serde(default)]
32    pub input_tokens: u32,
33
34    /// Output tokens generated.
35    #[serde(default)]
36    pub output_tokens: u32,
37
38    /// Total tokens (input + output).
39    #[serde(default)]
40    pub total_tokens: u32,
41}
42
43/// LLM completion information.
44///
45/// Immutable — set after the LLM responds.
46#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct CompletionExtension {
48    /// Why the model stopped generating.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub stop_reason: Option<StopReason>,
51
52    /// Token usage statistics.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub tokens: Option<TokenUsage>,
55
56    /// Model identifier.
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub model: Option<String>,
59
60    /// Raw response format (chatml, harmony, gemini, anthropic).
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub raw_format: Option<String>,
63
64    /// Creation timestamp (ISO 8601).
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub created_at: Option<String>,
67
68    /// Response latency in milliseconds.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub latency_ms: Option<u64>,
71}