Skip to main content

cookie_agent_models/adapters/
mod.rs

1//! Reviewed Oven adapter recipes used by Registry 1.
2
3mod 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/// Exact current Oven constructor family. Package presence does not add entries.
64#[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}