Skip to main content

systemprompt_models/wire/canonical/
response.rs

1//! The provider-neutral response and streaming-event model.
2//!
3//! Outbound adapters parse a buffered upstream reply into a
4//! [`CanonicalResponse`] or map upstream SSE bytes to a stream of
5//! [`CanonicalEvent`]s. Stop reasons are normalised here, with per-dialect
6//! string mappings.
7
8use super::request::CanonicalContent;
9
10#[derive(Debug, Clone, Copy, Default)]
11#[expect(
12    clippy::struct_field_names,
13    reason = "every field is a token count; the `_tokens` suffix is the domain vocabulary shared \
14              with the provider usage wire formats"
15)]
16pub struct CanonicalUsage {
17    pub input_tokens: u32,
18    pub output_tokens: u32,
19    pub cache_read_tokens: u32,
20    pub cache_creation_tokens: u32,
21    pub total_tokens: u32,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum CanonicalStopReason {
26    EndTurn,
27    MaxTokens,
28    StopSequence,
29    ToolUse,
30    Other,
31}
32
33impl CanonicalStopReason {
34    pub const fn anthropic_str(self) -> &'static str {
35        match self {
36            Self::MaxTokens => "max_tokens",
37            Self::StopSequence => "stop_sequence",
38            Self::ToolUse => "tool_use",
39            Self::EndTurn | Self::Other => "end_turn",
40        }
41    }
42
43    pub const fn openai_str(self) -> &'static str {
44        match self {
45            Self::MaxTokens => "length",
46            Self::ToolUse => "tool_calls",
47            Self::EndTurn | Self::StopSequence | Self::Other => "stop",
48        }
49    }
50
51    pub fn from_anthropic(s: &str) -> Self {
52        match s {
53            "end_turn" => Self::EndTurn,
54            "max_tokens" => Self::MaxTokens,
55            "stop_sequence" => Self::StopSequence,
56            "tool_use" => Self::ToolUse,
57            _ => Self::Other,
58        }
59    }
60
61    pub fn from_openai(s: &str) -> Self {
62        match s {
63            "stop" => Self::EndTurn,
64            "length" => Self::MaxTokens,
65            "tool_calls" | "function_call" => Self::ToolUse,
66            _ => Self::Other,
67        }
68    }
69}
70
71#[derive(Debug, Clone, Default)]
72pub struct GroundedSource {
73    pub uri: String,
74    pub title: Option<String>,
75    pub snippet: Option<String>,
76    pub relevance: Option<f32>,
77}
78
79#[derive(Debug, Clone, Default)]
80pub struct Grounding {
81    pub sources: Vec<GroundedSource>,
82    pub queries: Vec<String>,
83}
84
85#[derive(Debug, Clone, Default)]
86pub struct CodeExecutionOutput {
87    pub language: Option<String>,
88    pub code: String,
89    pub result: Option<String>,
90    pub outcome: Option<String>,
91}
92
93#[derive(Debug, Clone)]
94pub struct CanonicalResponse {
95    pub id: String,
96    pub model: String,
97    pub content: Vec<CanonicalContent>,
98    pub stop_reason: Option<CanonicalStopReason>,
99    pub usage: CanonicalUsage,
100    pub grounding: Option<Grounding>,
101    pub code_execution: Option<CodeExecutionOutput>,
102    pub raw_finish_reason: Option<String>,
103}
104
105#[derive(Debug, Clone)]
106pub enum CanonicalEvent {
107    MessageStart {
108        id: String,
109        model: String,
110        usage: CanonicalUsage,
111    },
112    ContentBlockStart {
113        index: u32,
114        block: ContentBlockKind,
115    },
116    TextDelta {
117        index: u32,
118        text: String,
119    },
120    ThinkingDelta {
121        index: u32,
122        text: String,
123    },
124    SignatureDelta {
125        index: u32,
126        signature: String,
127    },
128    ToolUseDelta {
129        index: u32,
130        partial_json: String,
131    },
132    ContentBlockStop {
133        index: u32,
134    },
135    UsageDelta(CanonicalUsage),
136    MessageStop {
137        id: String,
138        stop_reason: Option<CanonicalStopReason>,
139    },
140    Error(String),
141}
142
143#[derive(Debug, Clone)]
144pub enum ContentBlockKind {
145    Text,
146    Thinking {
147        signature: Option<String>,
148    },
149    ToolUse {
150        id: String,
151        name: String,
152        signature: Option<String>,
153    },
154}