Skip to main content

llm/
provider_connection.rs

1use std::collections::BTreeMap;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
4#[serde(rename_all = "kebab-case")]
5pub enum ProviderAuthMode {
6    #[default]
7    Default,
8    None,
9}
10
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
12pub struct ProviderConnectionConfig {
13    pub base_url: Option<String>,
14    pub auth_mode: ProviderAuthMode,
15    pub request_model: Option<String>,
16    pub inference_profile_arn: Option<String>,
17}
18
19#[doc = include_str!("docs/provider_connection_override.md")]
20#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
21#[serde(rename_all = "camelCase", deny_unknown_fields)]
22pub struct ProviderConnectionOverride {
23    /// Base URL override for the provider's API endpoint.
24    #[serde(default, rename = "url", skip_serializing_if = "Option::is_none")]
25    pub base_url: Option<String>,
26    /// Authentication mode. `default` uses the provider's normal credential
27    /// chain; `none` disables auth, for local or unauthenticated servers.
28    #[serde(default, rename = "auth", skip_serializing_if = "Option::is_none")]
29    pub auth_mode: Option<ProviderAuthMode>,
30    /// Provider-specific model or deployment target sent in requests without changing catalog identity.
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub request_model: Option<String>,
33    /// AWS Bedrock application inference profile ARN to route requests through.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub inference_profile_arn: Option<String>,
36}
37
38#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
39#[serde(transparent)]
40pub struct ProviderConnectionOverrides {
41    providers: BTreeMap<String, ProviderConnectionOverride>,
42}
43
44impl ProviderConnectionConfig {
45    pub fn from_override(value: ProviderConnectionOverride) -> Self {
46        Self {
47            base_url: value.base_url,
48            auth_mode: value.auth_mode.unwrap_or_default(),
49            request_model: value.request_model,
50            inference_profile_arn: value.inference_profile_arn,
51        }
52    }
53}
54
55impl ProviderConnectionOverride {
56    pub fn url(url: impl Into<String>) -> Self {
57        Self { base_url: Some(url.into()), ..Self::default() }
58    }
59
60    pub fn auth(auth_mode: ProviderAuthMode) -> Self {
61        Self { auth_mode: Some(auth_mode), ..Self::default() }
62    }
63
64    pub fn request_model(model: impl Into<String>) -> Self {
65        Self { request_model: Some(model.into()), ..Self::default() }
66    }
67
68    pub fn inference_profile_arn(arn: impl Into<String>) -> Self {
69        Self { inference_profile_arn: Some(arn.into()), ..Self::default() }
70    }
71
72    pub fn merge(&mut self, override_value: Self) {
73        if override_value.base_url.is_some() {
74            self.base_url = override_value.base_url;
75        }
76        if override_value.auth_mode.is_some() {
77            self.auth_mode = override_value.auth_mode;
78        }
79        if override_value.request_model.is_some() {
80            self.request_model = override_value.request_model;
81        }
82        if override_value.inference_profile_arn.is_some() {
83            self.inference_profile_arn = override_value.inference_profile_arn;
84        }
85    }
86}
87
88impl ProviderConnectionOverrides {
89    pub fn new(providers: BTreeMap<String, ProviderConnectionOverride>) -> Self {
90        Self { providers }
91    }
92
93    pub fn is_empty(&self) -> bool {
94        self.providers.is_empty()
95    }
96
97    pub fn merge(&mut self, overrides: ProviderConnectionOverrides) {
98        for (provider, override_value) in overrides.providers {
99            self.providers
100                .entry(provider)
101                .and_modify(|existing| existing.merge(override_value.clone()))
102                .or_insert(override_value);
103        }
104    }
105
106    pub fn config_for(&self, provider: &str) -> ProviderConnectionConfig {
107        self.providers.get(provider).cloned().map(ProviderConnectionConfig::from_override).unwrap_or_default()
108    }
109
110    pub fn into_inner(self) -> BTreeMap<String, ProviderConnectionOverride> {
111        self.providers
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn deserializes_and_merges_request_model() {
121        let mut first: ProviderConnectionOverrides =
122            serde_json::from_str(r#"{"azure-foundry":{"requestModel":"first"}}"#).unwrap();
123        first.merge(ProviderConnectionOverrides::new(BTreeMap::from([(
124            "azure-foundry".to_string(),
125            ProviderConnectionOverride::request_model("second"),
126        )])));
127
128        assert_eq!(first.config_for("azure-foundry").request_model.as_deref(), Some("second"));
129    }
130    #[test]
131    fn deserializes_bedrock_inference_profile_arn() {
132        let overrides: ProviderConnectionOverrides = serde_json::from_str(
133            r#"{"bedrock":{"inferenceProfileArn":"arn:aws:bedrock:us-west-2:000000000000:application-inference-profile/000000000000"}}"#,
134        )
135        .unwrap();
136
137        let config = overrides.config_for("bedrock");
138
139        assert_eq!(
140            config.inference_profile_arn.as_deref(),
141            Some("arn:aws:bedrock:us-west-2:000000000000:application-inference-profile/000000000000")
142        );
143    }
144
145    #[test]
146    fn merge_replaces_inference_profile_arn() {
147        let mut first = ProviderConnectionOverride::inference_profile_arn("arn:first");
148
149        first.merge(ProviderConnectionOverride::inference_profile_arn("arn:second"));
150
151        assert_eq!(first.inference_profile_arn.as_deref(), Some("arn:second"));
152    }
153
154    #[test]
155    fn provider_overrides_merge_inference_profile_arn() {
156        let mut first = ProviderConnectionOverrides::new(BTreeMap::from([(
157            "bedrock".to_string(),
158            ProviderConnectionOverride::inference_profile_arn("arn:first"),
159        )]));
160        let second = ProviderConnectionOverrides::new(BTreeMap::from([(
161            "bedrock".to_string(),
162            ProviderConnectionOverride::inference_profile_arn("arn:second"),
163        )]));
164
165        first.merge(second);
166
167        assert_eq!(first.config_for("bedrock").inference_profile_arn.as_deref(), Some("arn:second"));
168    }
169}