systemprompt_models/wire/canonical/
response.rs1use super::request::{CanonicalContent, flatten_part};
12use crate::wire::inspect::ForwardedSurface;
13
14#[derive(Debug, Clone, Copy, Default)]
15#[expect(
16 clippy::struct_field_names,
17 reason = "every field is a token count; the `_tokens` suffix is the domain vocabulary shared \
18 with the provider usage wire formats"
19)]
20pub struct CanonicalUsage {
21 pub input_tokens: u32,
22 pub output_tokens: u32,
23 pub cache_read_tokens: u32,
24 pub cache_creation_tokens: u32,
25 pub total_tokens: u32,
26}
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37#[expect(
38 clippy::struct_field_names,
39 reason = "every field is a token count; the `_tokens` suffix is the domain vocabulary shared \
40 with the provider usage wire formats"
41)]
42pub struct CanonicalUsageUpdate {
43 pub input_tokens: Option<u32>,
44 pub output_tokens: Option<u32>,
45 pub cache_read_tokens: Option<u32>,
46 pub cache_creation_tokens: Option<u32>,
47}
48
49impl CanonicalUsageUpdate {
50 #[must_use]
51 pub const fn is_empty(&self) -> bool {
52 self.input_tokens.is_none()
53 && self.output_tokens.is_none()
54 && self.cache_read_tokens.is_none()
55 && self.cache_creation_tokens.is_none()
56 }
57
58 pub const fn apply_to(&self, usage: &mut CanonicalUsage) {
59 if let Some(v) = self.input_tokens {
60 usage.input_tokens = v;
61 }
62 if let Some(v) = self.output_tokens {
63 usage.output_tokens = v;
64 }
65 if let Some(v) = self.cache_read_tokens {
66 usage.cache_read_tokens = v;
67 }
68 if let Some(v) = self.cache_creation_tokens {
69 usage.cache_creation_tokens = v;
70 }
71 usage.total_tokens = usage.input_tokens
72 + usage.output_tokens
73 + usage.cache_read_tokens
74 + usage.cache_creation_tokens;
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum CanonicalStopReason {
80 EndTurn,
81 MaxTokens,
82 StopSequence,
83 ToolUse,
84 Other,
85}
86
87impl CanonicalStopReason {
88 pub const fn anthropic_str(self) -> &'static str {
89 match self {
90 Self::MaxTokens => "max_tokens",
91 Self::StopSequence => "stop_sequence",
92 Self::ToolUse => "tool_use",
93 Self::EndTurn | Self::Other => "end_turn",
94 }
95 }
96
97 pub const fn openai_str(self) -> &'static str {
98 match self {
99 Self::MaxTokens => "length",
100 Self::ToolUse => "tool_calls",
101 Self::EndTurn | Self::StopSequence | Self::Other => "stop",
102 }
103 }
104
105 pub fn from_anthropic(s: &str) -> Self {
106 match s {
107 "end_turn" => Self::EndTurn,
108 "max_tokens" => Self::MaxTokens,
109 "stop_sequence" => Self::StopSequence,
110 "tool_use" => Self::ToolUse,
111 _ => Self::Other,
112 }
113 }
114
115 pub fn from_openai(s: &str) -> Self {
116 match s {
117 "stop" => Self::EndTurn,
118 "length" => Self::MaxTokens,
119 "tool_calls" | "function_call" => Self::ToolUse,
120 _ => Self::Other,
121 }
122 }
123}
124
125#[derive(Debug, Clone, Default)]
126pub struct GroundedSource {
127 pub uri: String,
128 pub title: Option<String>,
129 pub snippet: Option<String>,
130 pub relevance: Option<f32>,
131}
132
133#[derive(Debug, Clone, Default)]
134pub struct Grounding {
135 pub sources: Vec<GroundedSource>,
136 pub queries: Vec<String>,
137}
138
139#[derive(Debug, Clone, Default)]
140pub struct CodeExecutionOutput {
141 pub language: Option<String>,
142 pub code: String,
143 pub result: Option<String>,
144 pub outcome: Option<String>,
145}
146
147#[derive(Debug, Clone, Default)]
148pub struct CanonicalResponse {
149 pub id: String,
150 pub model: String,
151 pub content: Vec<CanonicalContent>,
152 pub stop_reason: Option<CanonicalStopReason>,
153 pub usage: CanonicalUsage,
154 pub grounding: Option<Grounding>,
155 pub code_execution: Option<CodeExecutionOutput>,
156 pub raw_finish_reason: Option<String>,
157 pub received_surface: ForwardedSurface,
158}
159
160impl CanonicalResponse {
161 pub fn content_units(&self) -> Vec<String> {
162 let mut units = Vec::with_capacity(self.content.len() + self.received_surface.len());
163 for part in &self.content {
164 let mut out = String::new();
165 flatten_part(&mut out, part);
166 if !out.is_empty() {
167 units.push(out);
168 }
169 }
170 for leaf in self.received_surface.leaves() {
171 units.push(leaf.value.clone());
172 }
173 units
174 }
175}
176
177#[derive(Debug, Clone)]
178pub enum CanonicalEvent {
179 MessageStart {
180 id: String,
181 model: String,
182 usage: CanonicalUsage,
183 },
184 ContentBlockStart {
185 index: u32,
186 block: ContentBlockKind,
187 },
188 TextDelta {
189 index: u32,
190 text: String,
191 },
192 ThinkingDelta {
193 index: u32,
194 text: String,
195 },
196 SignatureDelta {
197 index: u32,
198 signature: String,
199 },
200 EncryptedContentDelta {
201 index: u32,
202 data: String,
203 },
204 ToolUseDelta {
205 index: u32,
206 partial_json: String,
207 },
208 ContentBlockStop {
209 index: u32,
210 },
211 UsageDelta(CanonicalUsageUpdate),
212 MessageStop {
213 id: String,
214 stop_reason: Option<CanonicalStopReason>,
215 },
216 Error(String),
217}
218
219#[derive(Debug, Clone)]
220pub enum ContentBlockKind {
221 Text,
222 Thinking {
223 id: Option<String>,
224 signature: Option<String>,
225 },
226 ToolUse {
227 id: String,
228 name: String,
229 signature: Option<String>,
230 },
231}