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 #[serde(default)]
62 pub prompt_caching: bool,
63}
64
65#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
66pub struct ModelLimits {
67 #[serde(default)]
68 pub context_window: u32,
69
70 #[serde(default)]
71 pub max_output_tokens: u32,
72
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub max_thinking_budget: Option<u32>,
75}
76
77#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
78pub struct ModelPricing {
79 #[serde(default)]
80 pub input_per_million: f64,
81
82 #[serde(default)]
83 pub output_per_million: f64,
84
85 #[serde(default)]
86 pub cache_read_per_million: Option<f64>,
87
88 #[serde(default)]
89 pub cache_write_per_million: Option<f64>,
90
91 #[serde(default)]
92 pub per_image_cents: Option<f64>,
93}
94
95#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, schemars::JsonSchema)]
103#[serde(deny_unknown_fields)]
104pub struct ModelGovernance {
105 #[serde(default)]
106 pub european: bool,
107
108 #[serde(default)]
109 pub no_retain: bool,
110}
111
112impl ModelPricing {
113 #[must_use]
114 pub fn is_billable(&self) -> bool {
115 if self.per_image_cents.is_some_and(|c| c > 0.0) {
116 return true;
117 }
118 self.input_per_million > 0.0 && self.output_per_million > 0.0
119 }
120
121 #[must_use]
122 pub const fn declares_cache_rate(&self) -> bool {
123 self.cache_read_per_million.is_some()
124 }
125
126 #[must_use]
127 pub fn cache_read_rate(&self) -> f64 {
128 self.cache_read_per_million.unwrap_or(0.0)
129 }
130
131 #[must_use]
132 pub fn cache_write_rate(&self) -> f64 {
133 self.cache_write_per_million.unwrap_or(0.0)
134 }
135}
136
137#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
138pub struct ModelDefinition {
139 #[serde(default)]
140 pub capabilities: ModelCapabilities,
141
142 #[serde(default)]
143 pub limits: ModelLimits,
144
145 #[serde(default)]
146 pub pricing: ModelPricing,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct AiProviderConfig {
157 #[serde(default = "default_true")]
158 pub enabled: bool,
159
160 #[serde(default)]
161 pub default_model: String,
162
163 #[serde(default)]
164 pub default_image_model: String,
165
166 #[serde(default)]
167 pub google_search_enabled: bool,
168
169 #[serde(default)]
170 pub resilience: ResilienceSettings,
171}
172
173impl Default for AiProviderConfig {
174 fn default() -> Self {
175 Self {
176 enabled: true,
177 default_model: String::new(),
178 default_image_model: String::new(),
179 google_search_enabled: false,
180 resilience: ResilienceSettings::default(),
181 }
182 }
183}