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