1use serde::{Deserialize, Serialize};
3use serde_json::Value;
4mod continuation;
5pub use continuation::Continuation;
6pub use service::Image;
7
8#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct UseCase(pub String);
12
13#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct ModelMode(pub String);
17
18#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
20pub struct ModelConstraints {
21 pub vision: bool,
23 pub tool_calling: bool,
25 pub structured_output: bool,
27 pub reasoning: Option<String>,
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34#[derive(Default)]
35pub enum MessageRole {
36 System,
38 #[default]
40 User,
41 Assistant,
43 Tool,
45}
46
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49#[serde(tag = "type", rename_all = "snake_case")]
50pub enum ContentPart {
51 Text {
53 text: String,
55 },
56 Artifact {
58 uri: String,
60 mime_type: String,
62 },
63 Image { image: Image },
65 ToolCall(ToolCall),
67 ToolResult {
69 call_id: String,
71 result: Value,
73 is_error: bool,
75 },
76}
77
78#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80pub struct Message {
81 pub role: MessageRole,
83 pub content: Vec<ContentPart>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub continuation: Option<Continuation>,
88}
89
90#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
92pub struct ToolDefinition {
93 pub name: String,
94 pub description: String,
95 pub input_schema: Value,
96}
97#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
99pub struct ToolCall {
100 pub id: String,
102 pub name: String,
104 pub arguments: Value,
106}
107
108#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
110pub struct CompletionRequest {
111 pub use_case: UseCase,
113 pub model_mode: ModelMode,
115 pub messages: Vec<Message>,
117 pub tools: Vec<ToolDefinition>,
119 pub constraints: ModelConstraints,
121 pub max_output_tokens: Option<u32>,
123 pub diagnostics: bool,
125}
126
127#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
132pub struct ModelProfile {
133 pub profile_key: String,
135 pub context_window_tokens: u32,
137}
138
139#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142#[derive(Default)]
143pub enum FinishReason {
144 #[default]
146 Stop,
147 ToolCalls,
149 Length,
151 Other,
153}
154
155#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
157pub struct TokenUsage {
158 pub input_tokens: u64,
160 pub output_tokens: u64,
162 pub cached_input_tokens: Option<u64>,
164 pub reasoning_output_tokens: Option<u64>,
166 pub credits: Option<u64>,
168}
169
170#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
172pub struct Completion {
173 pub message: Message,
175 pub finish_reason: FinishReason,
177 pub usage: Option<TokenUsage>,
179 pub diagnostics: Option<Value>,
181}
182
183pub mod service;
184
185impl MessageRole {
186 pub const fn as_str(self) -> &'static str {
187 match self {
188 Self::System => "system",
189 Self::User => "user",
190 Self::Assistant => "assistant",
191 Self::Tool => "tool",
192 }
193 }
194}
195
196impl Default for Message {
197 fn default() -> Self {
198 Self::text(MessageRole::User, "")
199 }
200}
201impl Message {
202 pub fn text(role: MessageRole, text: impl Into<String>) -> Self {
203 Self {
204 role,
205 content: vec![ContentPart::Text { text: text.into() }],
206 continuation: None,
207 }
208 }
209 pub fn text_content(&self) -> String {
210 self.content
211 .iter()
212 .filter_map(|part| match part {
213 ContentPart::Text { text } => Some(text.as_str()),
214 _ => None,
215 })
216 .collect::<Vec<_>>()
217 .join("\n")
218 }
219}
220
221impl TokenUsage {
222 pub fn total_tokens(self) -> u64 {
223 self.input_tokens.saturating_add(self.output_tokens)
224 }
225}
226impl Message {
227 pub fn with_images(mut self, images: impl IntoIterator<Item = Image>) -> Self {
228 self.content
229 .extend(images.into_iter().map(|image| ContentPart::Image { image }));
230 self
231 }
232}
233
234pub const MESSAGE_SCHEMA: &str = include_str!("../schema/message.v1.schema.json");