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) {
62 if let Some(v) = self.input_tokens {
63 usage.input_tokens = v;
64 }
65 if let Some(v) = self.output_tokens {
66 usage.output_tokens = v;
67 }
68 if let Some(v) = self.cache_read_tokens {
69 usage.cache_read_tokens = v;
70 }
71 if let Some(v) = self.cache_creation_tokens {
72 usage.cache_creation_tokens = v;
73 }
74 usage.total_tokens = usage.input_tokens
75 + usage.output_tokens
76 + usage.cache_read_tokens
77 + usage.cache_creation_tokens;
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum CanonicalStopReason {
83 EndTurn,
84 MaxTokens,
85 StopSequence,
86 ToolUse,
87 Other,
88}
89
90impl CanonicalStopReason {
91 pub const fn anthropic_str(self) -> &'static str {
92 match self {
93 Self::MaxTokens => "max_tokens",
94 Self::StopSequence => "stop_sequence",
95 Self::ToolUse => "tool_use",
96 Self::EndTurn | Self::Other => "end_turn",
97 }
98 }
99
100 pub const fn openai_str(self) -> &'static str {
101 match self {
102 Self::MaxTokens => "length",
103 Self::ToolUse => "tool_calls",
104 Self::EndTurn | Self::StopSequence | Self::Other => "stop",
105 }
106 }
107
108 pub fn from_anthropic(s: &str) -> Self {
109 match s {
110 "end_turn" => Self::EndTurn,
111 "max_tokens" => Self::MaxTokens,
112 "stop_sequence" => Self::StopSequence,
113 "tool_use" => Self::ToolUse,
114 _ => Self::Other,
115 }
116 }
117
118 pub fn from_openai(s: &str) -> Self {
119 match s {
120 "stop" => Self::EndTurn,
121 "length" => Self::MaxTokens,
122 "tool_calls" | "function_call" => Self::ToolUse,
123 _ => Self::Other,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Default)]
129pub struct GroundedSource {
130 pub uri: String,
131 pub title: Option<String>,
132 pub snippet: Option<String>,
133 pub relevance: Option<f32>,
134}
135
136#[derive(Debug, Clone, Default)]
137pub struct Grounding {
138 pub sources: Vec<GroundedSource>,
139 pub queries: Vec<String>,
140}
141
142#[derive(Debug, Clone, Default)]
143pub struct CodeExecutionOutput {
144 pub language: Option<String>,
145 pub code: String,
146 pub result: Option<String>,
147 pub outcome: Option<String>,
148}
149
150#[derive(Debug, Clone, Default)]
151pub struct CanonicalResponse {
152 pub id: String,
153 pub model: String,
154 pub content: Vec<CanonicalContent>,
155 pub stop_reason: Option<CanonicalStopReason>,
156 pub usage: CanonicalUsage,
157 pub grounding: Option<Grounding>,
158 pub code_execution: Option<CodeExecutionOutput>,
159 pub raw_finish_reason: Option<String>,
160 pub received_surface: ForwardedSurface,
169}
170
171impl CanonicalResponse {
172 pub fn content_units(&self) -> Vec<String> {
176 let mut units = Vec::with_capacity(self.content.len() + self.received_surface.len());
177 for part in &self.content {
178 let mut out = String::new();
179 flatten_part(&mut out, part);
180 if !out.is_empty() {
181 units.push(out);
182 }
183 }
184 for leaf in self.received_surface.leaves() {
185 units.push(leaf.value.clone());
186 }
187 units
188 }
189}
190
191#[derive(Debug, Clone)]
192pub enum CanonicalEvent {
193 MessageStart {
194 id: String,
195 model: String,
196 usage: CanonicalUsage,
197 },
198 ContentBlockStart {
199 index: u32,
200 block: ContentBlockKind,
201 },
202 TextDelta {
203 index: u32,
204 text: String,
205 },
206 ThinkingDelta {
207 index: u32,
208 text: String,
209 },
210 SignatureDelta {
211 index: u32,
212 signature: String,
213 },
214 EncryptedContentDelta {
215 index: u32,
216 data: String,
217 },
218 ToolUseDelta {
219 index: u32,
220 partial_json: String,
221 },
222 ContentBlockStop {
223 index: u32,
224 },
225 UsageDelta(CanonicalUsageUpdate),
226 MessageStop {
227 id: String,
228 stop_reason: Option<CanonicalStopReason>,
229 },
230 Error(String),
231}
232
233#[derive(Debug, Clone)]
234pub enum ContentBlockKind {
235 Text,
236 Thinking {
237 id: Option<String>,
238 signature: Option<String>,
239 },
240 ToolUse {
241 id: String,
242 name: String,
243 signature: Option<String>,
244 },
245}