alien_core/bindings/
ai.rs1use super::BindingValue;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
13#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
14#[serde(tag = "service", rename_all = "lowercase")]
15pub enum AiBinding {
16 Bedrock(BedrockAiBinding),
18 Vertex(VertexAiBinding),
20 Foundry(FoundryAiBinding),
22 External(ExternalAiBinding),
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
29#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
30#[serde(rename_all = "camelCase")]
31pub struct BedrockAiBinding {
32 pub region: String,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
39#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
40#[serde(rename_all = "camelCase")]
41pub struct VertexAiBinding {
42 pub project: String,
44 pub location: String,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
51#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
52#[serde(rename_all = "camelCase")]
53pub struct FoundryAiBinding {
54 pub endpoint: String,
56 pub account: String,
58}
59
60#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
69#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
70#[serde(rename_all = "camelCase")]
71pub struct ExternalAiBinding {
72 pub provider: String,
74 pub api_key: BindingValue<String>,
77}
78
79impl std::fmt::Debug for ExternalAiBinding {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("ExternalAiBinding")
84 .field("provider", &self.provider)
85 .field("api_key", &"<redacted>")
86 .finish()
87 }
88}
89
90impl AiBinding {
91 pub const LOCAL_API_KEY_ENV: &'static str = "OPENAI_API_KEY";
95 pub const LOCAL_DEFAULT_PROVIDER: &'static str = "openai";
97
98 pub fn bedrock(region: impl Into<String>) -> Self {
99 Self::Bedrock(BedrockAiBinding {
100 region: region.into(),
101 })
102 }
103
104 pub fn vertex(project: impl Into<String>, location: impl Into<String>) -> Self {
105 Self::Vertex(VertexAiBinding {
106 project: project.into(),
107 location: location.into(),
108 })
109 }
110
111 pub fn foundry(endpoint: impl Into<String>, account: impl Into<String>) -> Self {
112 Self::Foundry(FoundryAiBinding {
113 endpoint: endpoint.into(),
114 account: account.into(),
115 })
116 }
117
118 pub fn external(
119 provider: impl Into<String>,
120 api_key: impl Into<BindingValue<String>>,
121 ) -> Self {
122 Self::External(ExternalAiBinding {
123 provider: provider.into(),
124 api_key: api_key.into(),
125 })
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_bedrock_binding_roundtrip() {
135 let binding = AiBinding::bedrock("us-east-1");
136
137 let json = serde_json::to_string(&binding).unwrap();
138 assert!(json.contains(r#""service":"bedrock""#));
139
140 let deserialized: AiBinding = serde_json::from_str(&json).unwrap();
141 assert_eq!(binding, deserialized);
142 }
143
144 #[test]
145 fn test_vertex_binding_roundtrip() {
146 let binding = AiBinding::vertex("my-project", "us-central1");
147
148 let json = serde_json::to_string(&binding).unwrap();
149 assert!(json.contains(r#""service":"vertex""#));
150
151 let deserialized: AiBinding = serde_json::from_str(&json).unwrap();
152 assert_eq!(binding, deserialized);
153 }
154
155 #[test]
156 fn test_foundry_binding_roundtrip() {
157 let binding = AiBinding::foundry("https://my-foundry.openai.azure.com", "my-subscription");
158
159 let json = serde_json::to_value(&binding).unwrap();
160 let json_str = json.to_string();
161 assert!(json_str.contains(r#""service":"foundry""#));
162 assert!(
163 !json_str.contains("instantAccess"),
164 "foundry binding must not serialize instant_access"
165 );
166
167 let deserialized: AiBinding = serde_json::from_value(json).unwrap();
168 assert_eq!(binding, deserialized);
169 }
170
171 #[test]
172 fn test_external_binding_roundtrip() {
173 let binding = AiBinding::external("openai", "sk-test-key");
174
175 let json = serde_json::to_value(&binding).unwrap();
178 let json_str = json.to_string();
179 assert!(json_str.contains(r#""apiKey""#), "external binding must serialize apiKey in camelCase");
180 assert_eq!(
181 json,
182 serde_json::json!({
183 "service": "external",
184 "provider": "openai",
185 "apiKey": "sk-test-key",
186 })
187 );
188
189 let deserialized: AiBinding =
190 serde_json::from_value(json).expect("external binding should round-trip");
191 assert_eq!(binding, deserialized);
192 }
193}