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 #[serde(rename = "external-ai")]
27 External(ExternalAiBinding),
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
33#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
34#[serde(rename_all = "camelCase")]
35pub struct BedrockAiBinding {
36 pub region: String,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
43#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
44#[serde(rename_all = "camelCase")]
45pub struct VertexAiBinding {
46 pub project: String,
48 pub location: String,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
55#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
56#[serde(rename_all = "camelCase")]
57pub struct FoundryAiBinding {
58 pub endpoint: String,
60 pub account: String,
62}
63
64#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
72#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
73#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
74#[serde(rename_all = "camelCase")]
75pub struct ExternalAiBinding {
76 pub provider: String,
78 pub api_key: BindingValue<String>,
81}
82
83impl std::fmt::Debug for ExternalAiBinding {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("ExternalAiBinding")
88 .field("provider", &self.provider)
89 .field("api_key", &"<redacted>")
90 .finish()
91 }
92}
93
94impl AiBinding {
95 pub const LOCAL_API_KEY_ENV: &'static str = "OPENAI_API_KEY";
99 pub const LOCAL_DEFAULT_PROVIDER: &'static str = "openai";
101
102 pub fn bedrock(region: impl Into<String>) -> Self {
103 Self::Bedrock(BedrockAiBinding {
104 region: region.into(),
105 })
106 }
107
108 pub fn vertex(project: impl Into<String>, location: impl Into<String>) -> Self {
109 Self::Vertex(VertexAiBinding {
110 project: project.into(),
111 location: location.into(),
112 })
113 }
114
115 pub fn foundry(endpoint: impl Into<String>, account: impl Into<String>) -> Self {
116 Self::Foundry(FoundryAiBinding {
117 endpoint: endpoint.into(),
118 account: account.into(),
119 })
120 }
121
122 pub fn external(
123 provider: impl Into<String>,
124 api_key: impl Into<BindingValue<String>>,
125 ) -> Self {
126 Self::External(ExternalAiBinding {
127 provider: provider.into(),
128 api_key: api_key.into(),
129 })
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_bedrock_binding_roundtrip() {
139 let binding = AiBinding::bedrock("us-east-1");
140
141 let json = serde_json::to_string(&binding).unwrap();
142 assert!(json.contains(r#""service":"bedrock""#));
143
144 let deserialized: AiBinding = serde_json::from_str(&json).unwrap();
145 assert_eq!(binding, deserialized);
146 }
147
148 #[test]
149 fn test_vertex_binding_roundtrip() {
150 let binding = AiBinding::vertex("my-project", "us-central1");
151
152 let json = serde_json::to_string(&binding).unwrap();
153 assert!(json.contains(r#""service":"vertex""#));
154
155 let deserialized: AiBinding = serde_json::from_str(&json).unwrap();
156 assert_eq!(binding, deserialized);
157 }
158
159 #[test]
160 fn test_foundry_binding_roundtrip() {
161 let binding = AiBinding::foundry("https://my-foundry.openai.azure.com", "my-subscription");
162
163 let json = serde_json::to_value(&binding).unwrap();
164 let json_str = json.to_string();
165 assert!(json_str.contains(r#""service":"foundry""#));
166 assert!(
167 !json_str.contains("instantAccess"),
168 "foundry binding must not serialize instant_access"
169 );
170
171 let deserialized: AiBinding = serde_json::from_value(json).unwrap();
172 assert_eq!(binding, deserialized);
173 }
174
175 #[test]
176 fn test_external_binding_roundtrip() {
177 let binding = AiBinding::external("openai", "sk-test-key");
178
179 let json = serde_json::to_value(&binding).unwrap();
182 let json_str = json.to_string();
183 assert!(json_str.contains(r#""apiKey""#), "external binding must serialize apiKey in camelCase");
184 assert_eq!(
185 json,
186 serde_json::json!({
187 "service": "external-ai",
188 "provider": "openai",
189 "apiKey": "sk-test-key",
190 })
191 );
192
193 let deserialized: AiBinding =
194 serde_json::from_value(json).expect("external binding should round-trip");
195 assert_eq!(binding, deserialized);
196 }
197}