1mod cache;
4mod capabilities;
5mod compaction;
6mod endpoints;
7mod headers;
8pub(crate) mod oven;
9
10pub use cache::{
11 BedrockCachePoint, BedrockCacheStrategy, BedrockCacheTtl, BedrockMessageCachePoint,
12 CacheStrategyConfig, GoogleCacheMode, GoogleCacheStrategyConfig, OpenAiCacheMode,
13 OpenAiCacheStrategyConfig, OpenAiPromptCacheRetention, OpenAiPromptCacheTtl,
14};
15pub use capabilities::{AdapterCapabilityError, validate_capability_ceiling};
16pub use compaction::with_native_compaction_instructions;
17pub use endpoints::{
18 BaseUrlOverridePolicy, EndpointBuildError, build_endpoint, custom_endpoint_policy,
19 managed_base_url_policy, validate_custom_endpoint, validate_managed_base_url,
20};
21pub use headers::{StaticHeaderError, validate_static_headers};
22pub use oven::{AnthropicCacheStrategyConfig, AnthropicCacheTtlConfig};
23
24use serde::{Deserialize, Serialize};
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
27pub struct WireAdapterSelection {
28 pub family_id: &'static str,
29 pub adapter_id: &'static str,
30 pub family: OvenAdapterFamily,
31}
32
33#[must_use]
34pub fn wire_adapter_for_protocol(adapter_id: &str) -> Option<OvenAdapterFamily> {
35 families()
36 .find(|family| family.protocol_recipe() == adapter_id)
37 .or_else(|| {
38 adapter_id
39 .starts_with("oven.openai-compatible.responses.")
40 .then_some(OvenAdapterFamily::OpenaiResponses)
41 })
42 .or_else(|| {
43 adapter_id
44 .starts_with("oven.openai-compatible.chat.")
45 .then_some(OvenAdapterFamily::OpenaiCompatible)
46 })
47 .or_else(|| {
48 adapter_id
49 .starts_with("oven.anthropic-compatible.messages.")
50 .then_some(OvenAdapterFamily::AnthropicCompatible)
51 })
52}
53
54#[must_use]
55pub const fn wire_adapter_for_custom(family: OvenAdapterFamily) -> WireAdapterSelection {
56 WireAdapterSelection {
57 family_id: "custom",
58 adapter_id: family.protocol_recipe(),
59 family,
60 }
61}
62
63#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
65#[serde(rename_all = "kebab-case")]
66pub enum OvenAdapterFamily {
67 Anthropic,
68 AnthropicCompatible,
69 OpenaiChat,
70 OpenaiResponses,
71 OpenaiCompatible,
72 GoogleGemini,
73 GoogleVertexGemini,
74 AwsBedrockConverse,
75 AzureOpenaiChat,
76 AzureOpenaiResponses,
77 CohereV2Chat,
78}
79
80impl OvenAdapterFamily {
81 pub(crate) fn with_endpoint(
82 self,
83 endpoint: Option<crate::authoring::RequestEndpoint>,
84 ) -> Result<Self, crate::compiler::DynamicCompileError> {
85 use crate::authoring::RequestEndpoint::{Completions, Responses};
86 Ok(match (self, endpoint) {
87 (_, None) => self,
88 (Self::OpenaiChat | Self::OpenaiResponses, Some(Completions)) => Self::OpenaiChat,
89 (
90 Self::OpenaiChat | Self::OpenaiResponses | Self::OpenaiCompatible,
91 Some(Responses),
92 ) => Self::OpenaiResponses,
93 (Self::OpenaiCompatible, Some(Completions)) => Self::OpenaiCompatible,
94 (Self::AzureOpenaiChat | Self::AzureOpenaiResponses, Some(Completions)) => {
95 Self::AzureOpenaiChat
96 }
97 (Self::AzureOpenaiChat | Self::AzureOpenaiResponses, Some(Responses)) => {
98 Self::AzureOpenaiResponses
99 }
100 _ => {
101 return Err(crate::compiler::DynamicCompileError::EndpointSelection(
102 format!(
103 "adaptor `{}` does not support request_endpoint; omit it to use the native API",
104 self.id()
105 ),
106 ));
107 }
108 })
109 }
110
111 pub(crate) fn automatic_replay(self, reasoning: bool) -> crate::ReplayCapability {
112 if reasoning && matches!(self, Self::Anthropic | Self::AnthropicCompatible) {
113 crate::ReplayCapability::Required
114 } else {
115 crate::ReplayCapability::Optional
116 }
117 }
118
119 #[must_use]
120 pub const fn id(self) -> &'static str {
121 match self {
122 Self::Anthropic => "anthropic",
123 Self::AnthropicCompatible => "anthropic-compatible",
124 Self::OpenaiChat => "openai-chat",
125 Self::OpenaiResponses => "openai-responses",
126 Self::OpenaiCompatible => "openai-compatible",
127 Self::GoogleGemini => "google-gemini",
128 Self::GoogleVertexGemini => "google-vertex-gemini",
129 Self::AwsBedrockConverse => "aws-bedrock-converse",
130 Self::AzureOpenaiChat => "azure-openai-chat",
131 Self::AzureOpenaiResponses => "azure-openai-responses",
132 Self::CohereV2Chat => "cohere-v2-chat",
133 }
134 }
135
136 #[must_use]
137 pub fn parse(value: &str) -> Option<Self> {
138 ALL.iter().copied().find(|family| family.id() == value)
139 }
140
141 #[must_use]
142 pub const fn protocol_recipe(self) -> &'static str {
143 match self {
144 Self::Anthropic => "oven.anthropic.messages",
145 Self::AnthropicCompatible => "oven.anthropic-compatible.messages",
146 Self::OpenaiChat => "oven.openai.chat",
147 Self::OpenaiResponses => "oven.openai.responses",
148 Self::OpenaiCompatible => "oven.openai-compatible.chat",
149 Self::GoogleGemini => "oven.google.gemini.generate-content",
150 Self::GoogleVertexGemini => "oven.google.vertex.generate-content",
151 Self::AwsBedrockConverse => "oven.bedrock.converse",
152 Self::AzureOpenaiChat => "oven.azure.openai.chat",
153 Self::AzureOpenaiResponses => "oven.azure.openai.responses",
154 Self::CohereV2Chat => "oven.cohere.chat-v2",
155 }
156 }
157
158 #[must_use]
159 pub const fn allowed_auth_methods(self) -> &'static [&'static str] {
160 match self {
161 Self::OpenaiCompatible => &["bearer-api-key-v1", "api-key-header-v1", "no-auth-v1"],
162 Self::OpenaiChat | Self::OpenaiResponses => &["bearer-api-key-v1", "no-auth-v1"],
163 Self::Anthropic => &["anthropic-api-key-v1"],
164 Self::AnthropicCompatible => {
165 &["anthropic-api-key-v1", "bearer-api-key-v1", "no-auth-v1"]
166 }
167 Self::GoogleGemini => &["google-api-key-header-v1"],
168 Self::GoogleVertexGemini => &["oauth-access-token-v1"],
169 Self::AwsBedrockConverse => &["aws-sigv4-credentials-v1"],
170 Self::AzureOpenaiChat | Self::AzureOpenaiResponses => &["azure-api-key-v1"],
171 Self::CohereV2Chat => &["bearer-api-key-v1"],
172 }
173 }
174}
175
176const ALL: &[OvenAdapterFamily] = &[
177 OvenAdapterFamily::Anthropic,
178 OvenAdapterFamily::AnthropicCompatible,
179 OvenAdapterFamily::OpenaiChat,
180 OvenAdapterFamily::OpenaiResponses,
181 OvenAdapterFamily::OpenaiCompatible,
182 OvenAdapterFamily::GoogleGemini,
183 OvenAdapterFamily::GoogleVertexGemini,
184 OvenAdapterFamily::AwsBedrockConverse,
185 OvenAdapterFamily::AzureOpenaiChat,
186 OvenAdapterFamily::AzureOpenaiResponses,
187 OvenAdapterFamily::CohereV2Chat,
188];
189
190const EMPTY_CUSTOM_SETUP: crate::recipes::SetupRecipe = crate::recipes::SetupRecipe {
191 id: "custom-no-setup-v1",
192 fields: &[],
193};
194const VERTEX_CUSTOM_FIELDS: &[crate::recipes::SetupFieldRecipe] = &[
195 crate::recipes::SetupFieldRecipe {
196 id: "location",
197 value_type: crate::recipes::SetupFieldType::String,
198 required: true,
199 default: None,
200 environment_alias: None,
201 },
202 crate::recipes::SetupFieldRecipe {
203 id: "project",
204 value_type: crate::recipes::SetupFieldType::String,
205 required: true,
206 default: None,
207 environment_alias: None,
208 },
209 crate::recipes::SetupFieldRecipe {
210 id: "resource",
211 value_type: crate::recipes::SetupFieldType::String,
212 required: false,
213 default: Some("publishers/google"),
214 environment_alias: None,
215 },
216];
217const VERTEX_CUSTOM_SETUP: crate::recipes::SetupRecipe = crate::recipes::SetupRecipe {
218 id: "custom-google-vertex-setup-v1",
219 fields: VERTEX_CUSTOM_FIELDS,
220};
221const BEDROCK_CUSTOM_FIELDS: &[crate::recipes::SetupFieldRecipe] =
222 &[crate::recipes::SetupFieldRecipe {
223 id: "region",
224 value_type: crate::recipes::SetupFieldType::String,
225 required: true,
226 default: None,
227 environment_alias: None,
228 }];
229const BEDROCK_CUSTOM_SETUP: crate::recipes::SetupRecipe = crate::recipes::SetupRecipe {
230 id: "custom-amazon-bedrock-setup-v1",
231 fields: BEDROCK_CUSTOM_FIELDS,
232};
233const AZURE_CUSTOM_FIELDS: &[crate::recipes::SetupFieldRecipe] = &[
234 crate::recipes::SetupFieldRecipe {
235 id: "api_version",
236 value_type: crate::recipes::SetupFieldType::String,
237 required: true,
238 default: None,
239 environment_alias: None,
240 },
241 crate::recipes::SetupFieldRecipe {
242 id: "deployment",
243 value_type: crate::recipes::SetupFieldType::String,
244 required: true,
245 default: None,
246 environment_alias: None,
247 },
248 crate::recipes::SetupFieldRecipe {
249 id: "model",
250 value_type: crate::recipes::SetupFieldType::String,
251 required: false,
252 default: None,
253 environment_alias: None,
254 },
255 crate::recipes::SetupFieldRecipe {
256 id: "version",
257 value_type: crate::recipes::SetupFieldType::String,
258 required: false,
259 default: None,
260 environment_alias: None,
261 },
262 crate::recipes::SetupFieldRecipe {
263 id: "deployment_type",
264 value_type: crate::recipes::SetupFieldType::String,
265 required: false,
266 default: None,
267 environment_alias: None,
268 },
269];
270const AZURE_CUSTOM_SETUP: crate::recipes::SetupRecipe = crate::recipes::SetupRecipe {
271 id: "custom-azure-openai-setup-v1",
272 fields: AZURE_CUSTOM_FIELDS,
273};
274
275#[must_use]
276pub const fn custom_setup_recipe(
277 family: OvenAdapterFamily,
278) -> &'static crate::recipes::SetupRecipe {
279 match family {
280 OvenAdapterFamily::GoogleVertexGemini => &VERTEX_CUSTOM_SETUP,
281 OvenAdapterFamily::AwsBedrockConverse => &BEDROCK_CUSTOM_SETUP,
282 OvenAdapterFamily::AzureOpenaiChat | OvenAdapterFamily::AzureOpenaiResponses => {
283 &AZURE_CUSTOM_SETUP
284 }
285 OvenAdapterFamily::Anthropic
286 | OvenAdapterFamily::AnthropicCompatible
287 | OvenAdapterFamily::OpenaiChat
288 | OvenAdapterFamily::OpenaiResponses
289 | OvenAdapterFamily::OpenaiCompatible
290 | OvenAdapterFamily::GoogleGemini
291 | OvenAdapterFamily::CohereV2Chat => &EMPTY_CUSTOM_SETUP,
292 }
293}
294
295pub fn families() -> impl ExactSizeIterator<Item = OvenAdapterFamily> {
296 ALL.iter().copied()
297}