Skip to main content

systemprompt_models/services/ai/
model.rs

1//! Per-provider AI policy and per-model descriptors.
2//!
3//! [`AiProviderConfig`] is the deployment policy layered on a registry provider
4//! (enable flag, default-model overrides, resilience). [`ModelDefinition`] and
5//! its [`ModelCapabilities`], [`ModelLimits`], and [`ModelPricing`] are the
6//! per-model descriptors shared with the services provider registry.
7//! Connectivity itself is never modelled here — it lives in the provider
8//! registry.
9//!
10//! Copyright (c) systemprompt.io — Business Source License 1.1.
11//! See <https://systemprompt.io> for licensing details.
12
13use 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/// Contractual/data-governance posture of a provider or model.
96///
97/// Used by gateway routes that require residency (`european`) or a
98/// no-train/no-retain clause (`no_retain`) before dispatching classified
99/// traffic. A model declares its own posture only to override the provider's;
100/// a model absent from the registry inherits the provider's, so adding a model
101/// never silently weakens what its provider already guarantees.
102#[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/// Per-provider AI *policy*, keyed by registry provider name.
150///
151/// Connectivity (endpoint, credential, model catalog) lives in the profile
152/// `providers` registry; this struct carries only the policy a deployment
153/// layers on top of an entry: whether the provider is enabled, its agent-side
154/// default-model override, image defaults, web-search toggle, and resilience.
155#[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}