systemprompt_models/services/ai/
model.rs1use serde::{Deserialize, Serialize};
14
15use super::config::ResilienceSettings;
16
17const fn default_true() -> bool {
18 true
19}
20
21#[expect(
22 clippy::struct_excessive_bools,
23 reason = "model capability matrix: each bool is an independent provider feature flag, not \
24 state"
25)]
26#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
27pub struct ModelCapabilities {
28 #[serde(default)]
29 pub vision: bool,
30
31 #[serde(default)]
32 pub audio_input: bool,
33
34 #[serde(default)]
35 pub video_input: bool,
36
37 #[serde(default)]
38 pub image_generation: bool,
39
40 #[serde(default)]
41 pub audio_generation: bool,
42
43 #[serde(default)]
44 pub streaming: bool,
45
46 #[serde(default)]
47 pub tools: bool,
48
49 #[serde(default)]
50 pub structured_output: bool,
51
52 #[serde(default)]
53 pub system_prompts: bool,
54
55 #[serde(default)]
56 pub image_resolution_config: bool,
57
58 #[serde(default)]
59 pub reasoning: bool,
60}
61
62#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
63pub struct ModelLimits {
64 #[serde(default)]
65 pub context_window: u32,
66
67 #[serde(default)]
68 pub max_output_tokens: u32,
69
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub max_thinking_budget: Option<u32>,
72}
73
74#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
75pub struct ModelPricing {
76 #[serde(default)]
77 pub input_per_million: f64,
78
79 #[serde(default)]
80 pub output_per_million: f64,
81
82 #[serde(default)]
83 pub cache_read_per_million: Option<f64>,
84
85 #[serde(default)]
86 pub cache_write_per_million: Option<f64>,
87
88 #[serde(default)]
89 pub per_image_cents: Option<f64>,
90}
91
92#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
100#[serde(deny_unknown_fields)]
101pub struct ModelGovernance {
102 #[serde(default)]
103 pub european: bool,
104
105 #[serde(default)]
106 pub no_retain: bool,
107}
108
109impl ModelPricing {
110 #[must_use]
111 pub fn is_billable(&self) -> bool {
112 if self.per_image_cents.is_some_and(|c| c > 0.0) {
113 return true;
114 }
115 self.input_per_million > 0.0 && self.output_per_million > 0.0
116 }
117
118 #[must_use]
119 pub const fn declares_cache_rate(&self) -> bool {
120 self.cache_read_per_million.is_some()
121 }
122
123 #[must_use]
124 pub fn cache_read_rate(&self) -> f64 {
125 self.cache_read_per_million.unwrap_or(0.0)
126 }
127
128 #[must_use]
129 pub fn cache_write_rate(&self) -> f64 {
130 self.cache_write_per_million.unwrap_or(0.0)
131 }
132}
133
134#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
135pub struct ModelDefinition {
136 #[serde(default)]
137 pub capabilities: ModelCapabilities,
138
139 #[serde(default)]
140 pub limits: ModelLimits,
141
142 #[serde(default)]
143 pub pricing: ModelPricing,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct AiProviderConfig {
154 #[serde(default = "default_true")]
155 pub enabled: bool,
156
157 #[serde(default)]
158 pub default_model: String,
159
160 #[serde(default)]
161 pub default_image_model: String,
162
163 #[serde(default)]
164 pub google_search_enabled: bool,
165
166 #[serde(default)]
167 pub resilience: ResilienceSettings,
168}
169
170impl Default for AiProviderConfig {
171 fn default() -> Self {
172 Self {
173 enabled: true,
174 default_model: String::new(),
175 default_image_model: String::new(),
176 google_search_enabled: false,
177 resilience: ResilienceSettings::default(),
178 }
179 }
180}