llmsdk_provider/language_model/finish_reason.rs
1//! Unified finish reason returned by a language model.
2//!
3//! Mirrors `language-model-v4-finish-reason.ts`. We pair the unified enum
4//! with the provider-raw string so callers can branch on either.
5// Rust guideline compliant 2026-02-21
6
7use serde::{Deserialize, Serialize};
8
9/// Why the model stopped generating.
10///
11/// `unified` is normalized across providers; `raw` preserves the original
12/// string for telemetry.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct FinishReason {
15 /// Normalized reason.
16 pub unified: FinishReasonKind,
17 /// Provider-reported raw value. `None` when not provided.
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub raw: Option<String>,
20}
21
22/// Normalized reason variants.
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
24#[serde(rename_all = "kebab-case")]
25pub enum FinishReasonKind {
26 /// Model generated a stop sequence.
27 Stop,
28 /// Model reached max-tokens.
29 Length,
30 /// Content filter triggered.
31 ContentFilter,
32 /// Model emitted tool calls.
33 ToolCalls,
34 /// Model stopped because of an error.
35 Error,
36 /// Anything else.
37 Other,
38}
39
40impl FinishReason {
41 /// Build a normalized reason without raw provider data.
42 #[must_use]
43 pub const fn new(kind: FinishReasonKind) -> Self {
44 Self {
45 unified: kind,
46 raw: None,
47 }
48 }
49
50 /// Build a normalized reason while preserving the provider string.
51 pub fn with_raw(kind: FinishReasonKind, raw: impl Into<String>) -> Self {
52 Self {
53 unified: kind,
54 raw: Some(raw.into()),
55 }
56 }
57}