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