Skip to main content

objectiveai_sdk/agent/openrouter/
provider.rs

1//! Provider routing preferences for Agents.
2//!
3//! These settings control how requests are routed to upstream model providers
4//! (e.g., via OpenRouter).
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashSet;
8use schemars::JsonSchema;
9
10/// Provider routing preferences.
11///
12/// Controls which providers are used and in what order when routing
13/// requests to upstream model hosts.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, JsonSchema, arbitrary::Arbitrary)]
15#[schemars(rename = "agent.openrouter.Provider")]
16pub struct Provider {
17    /// Whether to allow fallback to other providers if preferred ones fail.
18    /// Defaults to `true`.
19    #[serde(skip_serializing_if = "Option::is_none")]
20    #[schemars(extend("omitempty" = true))]
21    pub allow_fallbacks: Option<bool>,
22    /// Whether to require that the provider supports all request parameters.
23    /// Defaults to `false`.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    #[schemars(extend("omitempty" = true))]
26    pub require_parameters: Option<bool>,
27    /// Preferred provider order. Earlier providers are tried first.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    #[schemars(extend("omitempty" = true))]
30    pub order: Option<Vec<String>>,
31    /// Exclusive list of allowed providers. If set, only these providers are used.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    #[schemars(extend("omitempty" = true))]
34    pub only: Option<Vec<String>>,
35    /// Providers to exclude from routing.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    #[schemars(extend("omitempty" = true))]
38    pub ignore: Option<Vec<String>>,
39    /// Allowed model quantization levels.
40    #[serde(skip_serializing_if = "Option::is_none")]
41    #[schemars(extend("omitempty" = true))]
42    pub quantizations: Option<Vec<ProviderQuantization>>,
43}
44
45impl Provider {
46    /// Normalizes the provider configuration for deterministic hashing.
47    ///
48    /// Removes default values, empty collections, and deduplicates lists.
49    pub fn prepare(mut self) -> Option<Self> {
50        if let Some(true) = self.allow_fallbacks {
51            self.allow_fallbacks = None;
52        }
53        if let Some(false) = self.require_parameters {
54            self.require_parameters = None;
55        }
56        if let Some(order) = &mut self.order {
57            if order.is_empty() {
58                self.order = None;
59            } else {
60                let mut dedup = HashSet::with_capacity(order.len());
61                order.retain(|provider| dedup.insert(provider.clone()));
62            }
63        }
64        if let Some(only) = &mut self.only {
65            if only.is_empty() {
66                self.only = None;
67            } else {
68                only.sort();
69                only.dedup();
70            }
71        }
72        if let Some(ignore) = &mut self.ignore {
73            if ignore.is_empty() {
74                self.ignore = None;
75            } else {
76                ignore.sort();
77                ignore.dedup();
78            }
79        }
80        if let Some(quantizations) = &mut self.quantizations {
81            if quantizations.is_empty() {
82                self.quantizations = None;
83            } else {
84                quantizations.sort();
85                quantizations.dedup();
86            }
87        }
88        if self.allow_fallbacks.is_some()
89            || self.require_parameters.is_some()
90            || self.order.is_some()
91            || self.only.is_some()
92            || self.ignore.is_some()
93            || self.quantizations.is_some()
94        {
95            Some(self)
96        } else {
97            None
98        }
99    }
100
101    /// Validates that provider names are non-empty strings.
102    pub fn validate(&self) -> Result<(), String> {
103        if self.order.iter().any(|s| s.is_empty()) {
104            Err("`provider.order` strings cannot be empty".to_string())
105        } else if self.only.iter().any(|s| s.is_empty()) {
106            Err("`provider.only` strings cannot be empty".to_string())
107        } else if self.ignore.iter().any(|s| s.is_empty()) {
108            Err("`provider.ignore` strings cannot be empty".to_string())
109        } else {
110            Ok(())
111        }
112    }
113}
114
115/// Model quantization levels for provider filtering.
116///
117/// Quantization reduces model precision to decrease memory usage and
118/// increase inference speed, potentially at the cost of output quality.
119#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, arbitrary::Arbitrary)]
120#[serde(rename_all = "snake_case")]
121#[schemars(rename = "agent.openrouter.ProviderQuantization")]
122pub enum ProviderQuantization {
123    /// 4-bit integer quantization.
124    #[schemars(title = "Int4")]
125    Int4,
126    /// 8-bit integer quantization.
127    #[schemars(title = "Int8")]
128    Int8,
129    /// 4-bit floating point quantization.
130    #[schemars(title = "Fp4")]
131    Fp4,
132    /// 6-bit floating point quantization.
133    #[schemars(title = "Fp6")]
134    Fp6,
135    /// 8-bit floating point quantization.
136    #[schemars(title = "Fp8")]
137    Fp8,
138    /// 16-bit floating point (half precision).
139    #[schemars(title = "Fp16")]
140    Fp16,
141    /// 16-bit brain floating point.
142    #[schemars(title = "Bf16")]
143    Bf16,
144    /// 32-bit floating point (full precision).
145    #[schemars(title = "Fp32")]
146    Fp32,
147    /// Unknown quantization level.
148    #[schemars(title = "Unknown")]
149    Unknown,
150}
151
152impl PartialOrd for ProviderQuantization {
153    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
154        Some((*self as u16).cmp(&(*other as u16)))
155    }
156}
157
158impl Ord for ProviderQuantization {
159    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
160        self.partial_cmp(other).unwrap()
161    }
162}