From a810ceb08825a7827d629b604ef44686c6f51ce2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 00:31:06 +0000 Subject: [PATCH 1/3] feat(provider-utils): add provider headers and user-agent fetch Credential, AuthScheme and ProviderHeaders describe a provider's request headers as data and resolve them per request, the way each AI SDK package's getHeaders combines loadApiKey, its fixed headers, the user's headers and withUserAgentSuffix. UserAgentFetch and transport_user_agent make the user-agent edits of the AI SDK AWS transports. They move here from aimux-providers' shared module (RFC-0046 R1). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW --- aimux-provider-utils/src/lib.rs | 4 + aimux-provider-utils/src/provider_headers.rs | 162 ++++++++++++++++++ aimux-provider-utils/src/tests/mod.rs | 1 + .../src/tests/provider_headers.rs | 115 +++++++++++++ aimux-provider-utils/src/user_agent_fetch.rs | 71 ++++++++ 5 files changed, 353 insertions(+) create mode 100644 aimux-provider-utils/src/provider_headers.rs create mode 100644 aimux-provider-utils/src/tests/provider_headers.rs create mode 100644 aimux-provider-utils/src/user_agent_fetch.rs diff --git a/aimux-provider-utils/src/lib.rs b/aimux-provider-utils/src/lib.rs index 1b036baa..1c351090 100644 --- a/aimux-provider-utils/src/lib.rs +++ b/aimux-provider-utils/src/lib.rs @@ -26,6 +26,7 @@ pub mod logging; pub mod media_type; pub mod multipart; pub mod post_to_api; +pub mod provider_headers; pub mod read_response_with_size_limit; pub mod resolvable; pub mod resolve_provider_reference; @@ -35,6 +36,7 @@ pub mod sigv4_fetch; mod tests; pub mod transform_stream; pub mod url; +pub mod user_agent_fetch; /// WebSocket client for realtime provider APIs (RFC-0028). Empty unless the /// `ws` feature is enabled. #[cfg(feature = "ws")] @@ -63,6 +65,7 @@ pub use media_type::{ }; pub use multipart::{MultipartForm, media_type_to_extension}; pub use post_to_api::{post_form_data_to_api, post_json_to_api, post_to_api}; +pub use provider_headers::{AuthScheme, Credential, ProviderHeaders}; pub use resolvable::Resolvable; pub use resolve_provider_reference::resolve_provider_reference; pub use response_handler::{ @@ -74,3 +77,4 @@ pub use response_handler::{ pub use sigv4_fetch::{AwsCredentials, SigV4Fetch, SigV4Signing}; pub use transform_stream::{PipeThrough, TransformStreamController, Transformer, pipe_through}; pub use url::{validate_base_url, without_trailing_slash}; +pub use user_agent_fetch::{UserAgentFetch, transport_user_agent}; diff --git a/aimux-provider-utils/src/provider_headers.rs b/aimux-provider-utils/src/provider_headers.rs new file mode 100644 index 00000000..f1e67b1f --- /dev/null +++ b/aimux-provider-utils/src/provider_headers.rs @@ -0,0 +1,162 @@ +//! The provider-level request headers: where the credential comes from, how +//! it goes on the wire, and the fixed and user headers around it. Upstream +//! each package's `getHeaders` builds these with `loadApiKey`, +//! `combineHeaders` and `withUserAgentSuffix`; here the providers describe +//! them as data and resolve them per request (RFC-0046 §3.2). + +use aimux_core::AiMuxError; + +use crate::{ + HeaderMapOpt, Resolvable, combine_headers, load_api_key, normalize_headers, + with_user_agent_suffix, +}; + +/// Where the credential of a provider comes from. +#[derive(Clone, Debug)] +pub enum Credential { + /// No credential: no `Authorization` header is produced and no key is + /// resolved (a local server, or `auth: none` in a preset). + None, + /// The caller's key. It is used as given, `""` included, and never falls + /// back to the environment. A [`Resolvable::Future`] is awaited once, an + /// `AsyncFn` on every request. + Explicit(Resolvable), + /// Read the environment variable on every request; unset fails that + /// request with `AiMuxError::LoadApiKey`. + Env { var: String, description: String }, + /// Read the environment variable on every request; unset or empty sends + /// no credential (a local runtime started with or without a key). + OptionalEnv(String), +} + +impl Credential { + /// `Explicit` when the caller gave a key, otherwise `Env`. + #[must_use] + pub fn explicit_or_env( + api_key: Option>, + var: &str, + description: &str, + ) -> Self { + match api_key { + Some(key) => Self::Explicit(key), + None => Self::Env { + var: var.to_string(), + description: description.to_string(), + }, + } + } + + /// The secret of this request, `None` for [`Credential::None`]. A + /// package that puts the key somewhere other than a header (a query + /// parameter, a JWT claim) asks for it here, per request. + /// + /// # Errors + /// + /// The error of an `Explicit` key that fails to resolve, or + /// `AiMuxError::LoadApiKey` when the variable of `Env` is unset. + pub async fn secret(&self) -> Result, AiMuxError> { + match self { + Self::None => Ok(None), + Self::Explicit(key) => Ok(Some(key.resolve().await?)), + Self::Env { var, description } => Ok(Some(load_api_key(None, var, description)?)), + Self::OptionalEnv(var) => Ok(std::env::var(var).ok().filter(|key| !key.is_empty())), + } + } +} + +/// How the credential is put on the wire. +#[derive(Clone, Copy, Debug)] +pub enum AuthScheme { + /// `Authorization: Bearer `. + Bearer, + /// `: ` (`x-api-key`). + Header(&'static str), + /// `Authorization: ` (`Token `, `Key `). + Scheme(&'static str), +} + +/// The provider-level request headers, as data. They are produced on every +/// request: the credential first, then the fixed headers (organization, +/// project, version, ...), then the user's headers, which may override or +/// remove any of them (case-insensitively, `None` removes), and finally the +/// `ai-sdk-{package}/{version}` user-agent suffix when one is set. +#[derive(Clone, Debug)] +pub struct ProviderHeaders { + pub credential: Credential, + pub scheme: AuthScheme, + pub fixed: Vec<(String, String)>, + pub user: Option, + /// `(package, version)` of the user-agent suffix. + pub user_agent: Option<(&'static str, &'static str)>, +} + +impl ProviderHeaders { + /// Headers with `credential` put on the wire by `scheme`, no user-agent + /// suffix. + #[must_use] + pub fn new( + credential: Credential, + scheme: AuthScheme, + fixed: Vec<(String, String)>, + user: Option, + ) -> Self { + Self { + credential, + scheme, + fixed, + user, + user_agent: None, + } + } + + /// Headers with a bearer credential, no user-agent suffix. + #[must_use] + pub fn bearer( + credential: Credential, + fixed: Vec<(String, String)>, + user: Option, + ) -> Self { + Self::new(credential, AuthScheme::Bearer, fixed, user) + } + + /// The same headers with the `ai-sdk-{package}/{version}` user-agent + /// suffix. + #[must_use] + pub fn with_user_agent(mut self, package: &'static str, version: &'static str) -> Self { + self.user_agent = Some((package, version)); + self + } + + /// The headers of one request, in the order the type describes. + /// + /// # Errors + /// + /// The credential's error, as [`Credential::secret`] returns it. + pub async fn resolve(&self) -> Result { + let mut layer = HeaderMapOpt::new(); + if let Some(secret) = self.credential.secret().await? { + let (name, value) = match self.scheme { + AuthScheme::Bearer => ("Authorization", format!("Bearer {secret}")), + AuthScheme::Header(name) => (name, secret), + AuthScheme::Scheme(scheme) => ("Authorization", format!("{scheme} {secret}")), + }; + layer.insert(name.to_string(), Some(value)); + } + for (name, value) in &self.fixed { + layer.insert(name.clone(), Some(value.clone())); + } + let headers = match &self.user { + Some(user) => combine_headers(&[&layer, user]), + None => layer, + }; + let Some((package, version)) = self.user_agent else { + return Ok(headers); + }; + let mut headers = normalize_headers(headers).into_iter().collect(); + with_user_agent_suffix(&mut headers, &format!("ai-sdk-{package}/{version}")); + Ok(headers + .into_iter() + .map(|(name, value)| (name, Some(value))) + .collect()) + } +} diff --git a/aimux-provider-utils/src/tests/mod.rs b/aimux-provider-utils/src/tests/mod.rs index 6a5bd0d5..e35ff7a9 100644 --- a/aimux-provider-utils/src/tests/mod.rs +++ b/aimux-provider-utils/src/tests/mod.rs @@ -1,4 +1,5 @@ //! Ported upstream `@ai-sdk/provider-utils` tests, one file per upstream //! `.test.ts` (RFC-0044 L6). +mod provider_headers; mod resolve_provider_reference; diff --git a/aimux-provider-utils/src/tests/provider_headers.rs b/aimux-provider-utils/src/tests/provider_headers.rs new file mode 100644 index 00000000..7bf3c5f6 --- /dev/null +++ b/aimux-provider-utils/src/tests/provider_headers.rs @@ -0,0 +1,115 @@ +//! Tests of `ProviderHeaders` and `transport_user_agent`. Upstream has no +//! shared function or test file for them: each package's `getHeaders` and +//! AWS fetch build the same headers inline. + +use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT}; + +use crate::{ + AuthScheme, Credential, HeaderMapOpt, ProviderHeaders, Resolvable, transport_user_agent, +}; + +fn key(value: &str) -> Credential { + Credential::Explicit(Resolvable::Value(value.to_owned())) +} + +// Not an upstream case: the credential goes on the wire by its scheme, before +// the fixed headers. +#[tokio::test] +async fn puts_the_credential_on_the_wire_by_its_scheme() { + let fixed = vec![("OpenAI-Organization".to_owned(), "org".to_owned())]; + let bearer = ProviderHeaders::bearer(key("k"), fixed, None) + .resolve() + .await + .unwrap(); + assert_eq!(bearer["Authorization"].as_deref(), Some("Bearer k")); + assert_eq!(bearer["OpenAI-Organization"].as_deref(), Some("org")); + + let header = ProviderHeaders::new(key("k"), AuthScheme::Header("x-api-key"), Vec::new(), None) + .resolve() + .await + .unwrap(); + assert_eq!(header["x-api-key"].as_deref(), Some("k")); + + let scheme = ProviderHeaders::new(key("k"), AuthScheme::Scheme("Token"), Vec::new(), None) + .resolve() + .await + .unwrap(); + assert_eq!(scheme["Authorization"].as_deref(), Some("Token k")); + + let none = ProviderHeaders::bearer(Credential::None, Vec::new(), None) + .resolve() + .await + .unwrap(); + assert!(none.is_empty()); +} + +// Not an upstream case: the user's headers override or remove any of them, +// case-insensitively, and the user-agent suffix comes last. +#[tokio::test] +async fn lets_user_headers_override_and_appends_the_user_agent_suffix() { + let user = HeaderMapOpt::from([ + ("authorization".to_owned(), None), + ("X-Custom".to_owned(), Some("1".to_owned())), + ("User-Agent".to_owned(), Some("app/1".to_owned())), + ]); + let headers = ProviderHeaders::bearer(key("k"), Vec::new(), Some(user)) + .with_user_agent("openai", "4.0.83") + .resolve() + .await + .unwrap(); + + assert!( + !headers + .keys() + .any(|name| name.eq_ignore_ascii_case("authorization")) + ); + assert_eq!(headers["x-custom"].as_deref(), Some("1")); + assert_eq!( + headers["user-agent"].as_deref(), + Some("app/1 ai-sdk-openai/4.0.83") + ); +} + +// Not an upstream case: an unset environment variable fails the request for +// `Env` and sends no credential for `OptionalEnv`. +#[tokio::test] +async fn reads_environment_credentials_per_request() { + let var = "AIMUX_PROVIDER_HEADERS_TEST_UNSET_KEY"; + let env = Credential::Env { + var: var.to_owned(), + description: "Test".to_owned(), + }; + assert!(env.secret().await.is_err()); + assert_eq!( + Credential::OptionalEnv(var.to_owned()) + .secret() + .await + .unwrap(), + None + ); +} + +// Not an upstream case: the last `ai-sdk-provider-utils/` part is renamed +// and the suffix appended; with neither, the header is left as it is. +#[test] +fn rewrites_the_provider_utils_part_and_appends_the_suffix() { + let mut headers = HeaderMap::new(); + headers.insert( + USER_AGENT, + HeaderValue::from_static("app/1 ai-sdk-provider-utils/5.0.53"), + ); + transport_user_agent( + &mut headers, + Some("5.0.51"), + Some("ai-sdk/amazon-bedrock/5.0.100"), + ); + assert_eq!( + headers[USER_AGENT], + "app/1 ai-sdk-provider-utils/5.0.51 ai-sdk/amazon-bedrock/5.0.100" + ); + + let mut untouched = HeaderMap::new(); + untouched.insert(USER_AGENT, HeaderValue::from_static("app/1")); + transport_user_agent(&mut untouched, Some("5.0.51"), None); + assert_eq!(untouched[USER_AGENT], "app/1"); +} diff --git a/aimux-provider-utils/src/user_agent_fetch.rs b/aimux-provider-utils/src/user_agent_fetch.rs new file mode 100644 index 00000000..404ea1eb --- /dev/null +++ b/aimux-provider-utils/src/user_agent_fetch.rs @@ -0,0 +1,71 @@ +//! The user-agent edits a transport makes after the request helpers have +//! built the header (RFC-0046 §3.2). + +use crate::FetchFunction; + +/// A transport decorator that edits the request's `user-agent` just before +/// forwarding it, after the helper layer has added +/// `ai-sdk-provider-utils/`: it names `provider_utils` in that part (when +/// set) and appends `suffix` (when set). The suffix is the AI SDK AWS +/// transports' own `withUserAgentSuffix` (amazon-bedrock-sigv4-fetch.ts:42-46 +/// and :126-130, anthropic-aws-fetch.ts:42-46 and :126-130); the runtime +/// fragment they append is left out, as everywhere in aimux. +pub struct UserAgentFetch { + /// `None` is the process default transport, resolved per request. + pub inner: Option, + pub provider_utils: Option<&'static str>, + pub suffix: Option, +} + +#[async_trait::async_trait] +impl crate::Fetch for UserAgentFetch { + async fn fetch( + &self, + mut request: crate::FetchRequest, + ) -> Result { + transport_user_agent( + &mut request.headers, + self.provider_utils, + self.suffix.as_deref(), + ); + let inner = self.inner.clone().unwrap_or_else(crate::default_fetch); + inner.fetch(request).await + } +} + +/// Rewrite the last `ai-sdk-provider-utils/` part of the `user-agent` to +/// `provider_utils` (when set), then append `suffix` (when set, creating the +/// header if absent). A value that is not a valid header is left as it was. +pub fn transport_user_agent( + headers: &mut reqwest::header::HeaderMap, + provider_utils: Option<&str>, + suffix: Option<&str>, +) { + let current = headers + .get(reqwest::header::USER_AGENT) + .and_then(|value| value.to_str().ok()) + .unwrap_or_default(); + let mut parts: Vec = current + .split(' ') + .filter(|part| !part.is_empty()) + .map(String::from) + .collect(); + let last_utils = parts + .iter_mut() + .rev() + .find(|part| part.starts_with("ai-sdk-provider-utils/")); + match (provider_utils, last_utils) { + (Some(version), Some(part)) => *part = format!("ai-sdk-provider-utils/{version}"), + // Nothing to rename or append: the header stays exactly as it is. + _ if suffix.is_none() => return, + _ => {} + } + parts.extend(suffix.map(String::from)); + let user_agent = parts.join(" "); + if user_agent == current { + return; + } + if let Ok(value) = reqwest::header::HeaderValue::try_from(user_agent) { + headers.insert(reqwest::header::USER_AGENT, value); + } +} From 9990433bd980659e91064e7d82e24e52ac3ec28e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 00:31:07 +0000 Subject: [PATCH 2/3] refactor(providers): build request headers with provider-utils Every provider imports Credential, AuthScheme, ProviderHeaders, UserAgentFetch and transport_user_agent from provider-utils; the shared copies are gone. Headers are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW --- aimux-providers/src/anthropic/config.rs | 2 +- aimux-providers/src/anthropic/provider.rs | 4 +- aimux-providers/src/anthropic_aws/fetch.rs | 2 +- aimux-providers/src/assemblyai.rs | 5 +- aimux-providers/src/azure/openai_provider.rs | 3 +- aimux-providers/src/bedrock/sigv4_fetch.rs | 2 +- aimux-providers/src/bedrock/tests/support.rs | 2 +- aimux-providers/src/black_forest_labs.rs | 5 +- aimux-providers/src/cartesia.rs | 3 +- aimux-providers/src/codex.rs | 4 +- aimux-providers/src/cohere/provider.rs | 3 +- aimux-providers/src/dataforseo.rs | 3 +- aimux-providers/src/decision_support.rs | 2 +- aimux-providers/src/deepgram.rs | 3 +- aimux-providers/src/deepseek/provider.rs | 3 +- aimux-providers/src/elevenlabs.rs | 5 +- aimux-providers/src/exa_ai.rs | 3 +- aimux-providers/src/fal.rs | 5 +- aimux-providers/src/firecrawl.rs | 3 +- aimux-providers/src/gladia.rs | 5 +- aimux-providers/src/google/provider.rs | 3 +- .../src/google/tests/language_model.rs | 3 +- aimux-providers/src/google_pse.rs | 3 +- aimux-providers/src/groq/provider.rs | 3 +- aimux-providers/src/huggingface/provider.rs | 3 +- aimux-providers/src/hume.rs | 3 +- aimux-providers/src/jina_ai.rs | 3 +- aimux-providers/src/klingai.rs | 3 +- aimux-providers/src/linkup.rs | 3 +- aimux-providers/src/lmnt.rs | 3 +- aimux-providers/src/luma.rs | 5 +- aimux-providers/src/mistral/provider.rs | 3 +- .../src/open_responses/provider.rs | 5 +- aimux-providers/src/openai/config.rs | 2 +- aimux-providers/src/openai/provider.rs | 2 +- .../responses/convert_to_responses_input.rs | 2 +- .../responses/responses_language_model.rs | 4 +- .../src/openai_compatible/provider.rs | 2 +- .../tests/chat/chat_language_model.rs | 4 +- .../completion/completion_language_model.rs | 2 +- .../tests/image/image_model.rs | 2 +- aimux-providers/src/parallel_ai.rs | 3 +- aimux-providers/src/preset.rs | 4 +- aimux-providers/src/prodia.rs | 3 +- aimux-providers/src/recraft.rs | 6 +- aimux-providers/src/replicate.rs | 4 +- aimux-providers/src/revai.rs | 4 +- aimux-providers/src/runwayml.rs | 3 +- aimux-providers/src/searxng.rs | 3 +- aimux-providers/src/serper.rs | 3 +- aimux-providers/src/shared/exchange.rs | 3 +- aimux-providers/src/shared/mod.rs | 212 +----------------- aimux-providers/src/stability.rs | 3 +- aimux-providers/src/tavily.rs | 3 +- aimux-providers/src/tinyfish.rs | 3 +- .../vertex/anthropic/anthropic_provider.rs | 2 +- .../src/vertex/maas/maas_provider.rs | 6 +- .../src/vertex/xai/xai_provider.rs | 6 +- aimux-providers/src/voyage/mod.rs | 3 +- aimux-providers/src/xai/provider.rs | 3 +- aimux-providers/src/you_com.rs | 3 +- 61 files changed, 116 insertions(+), 294 deletions(-) diff --git a/aimux-providers/src/anthropic/config.rs b/aimux-providers/src/anthropic/config.rs index 529e8a5b..42308c5b 100644 --- a/aimux-providers/src/anthropic/config.rs +++ b/aimux-providers/src/anthropic/config.rs @@ -15,9 +15,9 @@ use std::sync::Arc; use serde_json::Value; -use crate::shared::{Credential, ProviderHeaders}; use aimux_core::AiMuxError; use aimux_core::language_model::SupportedUrls; +use aimux_provider_utils::{Credential, ProviderHeaders}; use aimux_provider_utils::{ ExchangeContext, FetchFunction, HeaderMapOpt, HttpRequest, ResponseHandler, combine_headers, normalize_headers, diff --git a/aimux-providers/src/anthropic/provider.rs b/aimux-providers/src/anthropic/provider.rs index 2939d51d..a82cf480 100644 --- a/aimux-providers/src/anthropic/provider.rs +++ b/aimux-providers/src/anthropic/provider.rs @@ -4,7 +4,6 @@ use std::sync::{Arc, OnceLock}; use futures::future::BoxFuture; -use crate::shared::ProviderHeaders; use aimux_core::decision_model::DecisionModel; use aimux_core::embedding_model::EmbeddingModel; use aimux_core::error::AiMuxError; @@ -13,11 +12,12 @@ use aimux_core::image_model::ImageModel; use aimux_core::language_model::{LanguageModel, SupportedUrls}; use aimux_core::model_catalogue::RuntimeModel; use aimux_core::provider::{Provider, ProviderDiscovery}; +use aimux_provider_utils::ProviderHeaders; use aimux_provider_utils::{ DecisionLanguageModel, FetchFunction, HeaderMapOpt, Resolvable, validate_base_url, }; -use crate::shared::{AuthScheme, Credential}; +use aimux_provider_utils::{AuthScheme, Credential}; use super::config::{AnthropicEndpoint, AnthropicModelConfig, AnthropicModelHooks}; use super::language_model_options; diff --git a/aimux-providers/src/anthropic_aws/fetch.rs b/aimux-providers/src/anthropic_aws/fetch.rs index f93d199e..409a4d3f 100644 --- a/aimux-providers/src/anthropic_aws/fetch.rs +++ b/aimux-providers/src/anthropic_aws/fetch.rs @@ -258,7 +258,7 @@ impl Fetch for AnthropicAwsFetch { // `ai-sdk-provider-utils/` and before any signing, on the API-key // and SigV4 paths alike (anthropic-aws-fetch.ts:42-46, :126-130). let package = crate::shared::aisdk_pins::ANTHROPIC_AWS; - crate::shared::transport_user_agent( + aimux_provider_utils::transport_user_agent( &mut request.headers, Some(package.provider_utils), Some(&format!("ai-sdk-{}/{}", package.name, package.version)), diff --git a/aimux-providers/src/assemblyai.rs b/aimux-providers/src/assemblyai.rs index 7e8f2e7f..e6366cc1 100644 --- a/aimux-providers/src/assemblyai.rs +++ b/aimux-providers/src/assemblyai.rs @@ -32,9 +32,10 @@ use aimux_provider_utils::HttpBody; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use crate::shared::{ - AuthScheme, Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, ProviderHeaders, - is_poll_control_key, poll_interval_ms, poll_until, + EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, is_poll_control_key, poll_interval_ms, + poll_until, }; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// AssemblyAI errors: `{"error": "..."}` where `error` is a plain string /// (e.g. `"Authentication error, API token missing/invalid."`). No machine diff --git a/aimux-providers/src/azure/openai_provider.rs b/aimux-providers/src/azure/openai_provider.rs index e47c1fd4..61ab5e3b 100644 --- a/aimux-providers/src/azure/openai_provider.rs +++ b/aimux-providers/src/azure/openai_provider.rs @@ -29,7 +29,8 @@ use crate::openai::{ OpenAIEmbeddingModel, OpenAIImageModel, OpenAIModel, OpenAIResponsesModel, OpenAISpeechModel, OpenAITranscriptionModel, }; -use crate::shared::{AuthScheme, Credential, ProviderHeaders, is_valid_hostname_part}; +use crate::shared::is_valid_hostname_part; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; use super::options; use super::transcription_model_options::{Api, AzureOptions, is_default_speech, parse_options}; diff --git a/aimux-providers/src/bedrock/sigv4_fetch.rs b/aimux-providers/src/bedrock/sigv4_fetch.rs index 816475e3..dd11aeed 100644 --- a/aimux-providers/src/bedrock/sigv4_fetch.rs +++ b/aimux-providers/src/bedrock/sigv4_fetch.rs @@ -163,7 +163,7 @@ impl Fetch for BedrockAuthFetch { // and before signing, like amazon-bedrock-sigv4-fetch.ts:42-46 // (SigV4) and :126-130 (API key). let package = crate::shared::aisdk_pins::AMAZON_BEDROCK; - crate::shared::transport_user_agent( + aimux_provider_utils::transport_user_agent( &mut request.headers, Some(package.provider_utils), Some(&format!("ai-sdk-{}/{}", package.name, package.version)), diff --git a/aimux-providers/src/bedrock/tests/support.rs b/aimux-providers/src/bedrock/tests/support.rs index bd5fa026..7e19d029 100644 --- a/aimux-providers/src/bedrock/tests/support.rs +++ b/aimux-providers/src/bedrock/tests/support.rs @@ -29,7 +29,7 @@ impl Fetch for InjectFetchHeaders { for (name, value) in &self.custom_headers { request.headers.insert(name.clone(), value.clone()); } - crate::shared::transport_user_agent( + aimux_provider_utils::transport_user_agent( &mut request.headers, None, Some(BEDROCK_USER_AGENT_PART), diff --git a/aimux-providers/src/black_forest_labs.rs b/aimux-providers/src/black_forest_labs.rs index f534bc8d..965e5651 100644 --- a/aimux-providers/src/black_forest_labs.rs +++ b/aimux-providers/src/black_forest_labs.rs @@ -30,9 +30,10 @@ use aimux_provider_utils::HttpRequest; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use crate::shared::{ - Credential, EndpointConfig, POLL_INTERVAL_MILLIS_KEY, PollStep, ProviderHeaders, - is_poll_control_key, poll_interval_ms, poll_until, retry_download, + EndpointConfig, POLL_INTERVAL_MILLIS_KEY, PollStep, is_poll_control_key, poll_interval_ms, + poll_until, retry_download, }; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// AI SDK's `isTrustedUrl` (black-forest-labs-api.ts): credentials may go to /// the configured origin, or over HTTPS to `bfl.ai` and its subdomains — BFL diff --git a/aimux-providers/src/cartesia.rs b/aimux-providers/src/cartesia.rs index 26298a11..b770fbf9 100644 --- a/aimux-providers/src/cartesia.rs +++ b/aimux-providers/src/cartesia.rs @@ -37,7 +37,8 @@ use aimux_core::speech_model::{ use aimux_provider_utils::HttpBody; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn cartesia_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/codex.rs b/aimux-providers/src/codex.rs index f868d409..c4f3d77d 100644 --- a/aimux-providers/src/codex.rs +++ b/aimux-providers/src/codex.rs @@ -48,7 +48,7 @@ use aimux_core::provider::{Provider, ProviderDiscovery}; use aimux_core::result::{GenerateResult, StreamResult}; use aimux_core::types::Warning; -use crate::shared::ProviderHeaders; +use aimux_provider_utils::ProviderHeaders; use aimux_provider_utils::{ FetchFunction, HeaderMapOpt, HttpRequest, Resolvable, validate_base_url, }; @@ -61,7 +61,7 @@ use crate::openai::responses::{ OpenAIResponsesModel, ResponsesNamespace, build_responses_request_body, }; pub use crate::shared::TransformRequestBody; -use crate::shared::{AuthScheme, Credential}; +use aimux_provider_utils::{AuthScheme, Credential}; /// Default API-key base URL (official OpenAI Responses endpoint). pub const CODEX_API_BASE_URL: &str = "https://api.openai.com/v1"; diff --git a/aimux-providers/src/cohere/provider.rs b/aimux-providers/src/cohere/provider.rs index 798f0218..d4fde84e 100644 --- a/aimux-providers/src/cohere/provider.rs +++ b/aimux-providers/src/cohere/provider.rs @@ -14,7 +14,8 @@ use aimux_core::provider::{Provider, ProviderDiscovery}; use aimux_core::reranking_model::RerankingModel; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, without_trailing_slash}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; use super::error::cohere_failed_response_handler; use super::{CohereEmbeddingModel, CohereModel, CohereRerankingModel}; diff --git a/aimux-providers/src/dataforseo.rs b/aimux-providers/src/dataforseo.rs index ae063835..66e23fa6 100644 --- a/aimux-providers/src/dataforseo.rs +++ b/aimux-providers/src/dataforseo.rs @@ -25,7 +25,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, EndpointHeaders}; +use crate::shared::{EndpointConfig, EndpointHeaders}; +use aimux_provider_utils::Credential; /// Fixed model id for the DataForSEO search model. const MODEL_ID: &str = "dataforseo-search"; diff --git a/aimux-providers/src/decision_support.rs b/aimux-providers/src/decision_support.rs index c82242ad..14c320d2 100644 --- a/aimux-providers/src/decision_support.rs +++ b/aimux-providers/src/decision_support.rs @@ -278,7 +278,7 @@ pub(crate) fn options( #[cfg(test)] mod tests { use crate::openai::config::OpenAIUrl; - use crate::shared::{Credential, ProviderHeaders}; + use aimux_provider_utils::{Credential, ProviderHeaders}; use super::*; diff --git a/aimux-providers/src/deepgram.rs b/aimux-providers/src/deepgram.rs index 3d9b0d0d..75ba6fda 100644 --- a/aimux-providers/src/deepgram.rs +++ b/aimux-providers/src/deepgram.rs @@ -31,7 +31,8 @@ use aimux_core::transcription_model::{ use aimux_provider_utils::HttpBody; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Deepgram errors: `{"err_code": "...", "err_msg": "...", "request_id": ...}` /// on most endpoints; some return `{"category": "...", "message": "...", diff --git a/aimux-providers/src/deepseek/provider.rs b/aimux-providers/src/deepseek/provider.rs index 63ee413c..b32a1ffd 100644 --- a/aimux-providers/src/deepseek/provider.rs +++ b/aimux-providers/src/deepseek/provider.rs @@ -16,7 +16,8 @@ use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_bas use super::chat::{ DeepSeekChatConfig, DeepSeekChatLanguageModel, deepseek_failed_response_handler, }; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders, SupportedUrlsSource}; +use crate::shared::{EndpointConfig, SupportedUrlsSource}; +use aimux_provider_utils::{Credential, ProviderHeaders}; pub(crate) const DEFAULT_BASE_URL: &str = "https://api.deepseek.com"; pub(crate) const API_KEY_ENV_VAR: &str = "DEEPSEEK_API_KEY"; diff --git a/aimux-providers/src/elevenlabs.rs b/aimux-providers/src/elevenlabs.rs index e0bb81b3..3cd8f5c7 100644 --- a/aimux-providers/src/elevenlabs.rs +++ b/aimux-providers/src/elevenlabs.rs @@ -31,9 +31,8 @@ use aimux_core::language_model::LanguageModel; use aimux_core::provider::Provider; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, HttpBody, Resolvable, validate_base_url}; -use crate::shared::{ - AuthScheme, Credential, EndpointConfig, ProviderHeaders, TransformRequestBody, -}; +use crate::shared::{EndpointConfig, TransformRequestBody}; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; pub(crate) mod options; diff --git a/aimux-providers/src/exa_ai.rs b/aimux-providers/src/exa_ai.rs index 767b6094..e9d3c7c3 100644 --- a/aimux-providers/src/exa_ai.rs +++ b/aimux-providers/src/exa_ai.rs @@ -21,7 +21,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; fn exa_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/fal.rs b/aimux-providers/src/fal.rs index f2cea67b..b27e8e23 100644 --- a/aimux-providers/src/fal.rs +++ b/aimux-providers/src/fal.rs @@ -28,9 +28,10 @@ use aimux_provider_utils::HttpRequest; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use crate::shared::{ - AuthScheme, Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, ProviderHeaders, - is_poll_control_key, poll_interval_ms, poll_until, retry_download, + EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, is_poll_control_key, poll_interval_ms, + poll_until, retry_download, }; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// fal errors are FastAPI-style: `{"detail": "..."}` or /// `{"detail": [{"loc": [...], "msg": "...", "type": "..."}]}` where `type` diff --git a/aimux-providers/src/firecrawl.rs b/aimux-providers/src/firecrawl.rs index 5990ed6b..fc9a434a 100644 --- a/aimux-providers/src/firecrawl.rs +++ b/aimux-providers/src/firecrawl.rs @@ -21,7 +21,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; const MODEL_ID: &str = "firecrawl-search"; diff --git a/aimux-providers/src/gladia.rs b/aimux-providers/src/gladia.rs index e78561f8..d5d911b6 100644 --- a/aimux-providers/src/gladia.rs +++ b/aimux-providers/src/gladia.rs @@ -31,9 +31,10 @@ use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_bas use aimux_provider_utils::{HttpBody, MultipartForm, media_type_to_extension}; use crate::shared::{ - Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, ProviderHeaders, - is_poll_control_key, poll_interval_ms, poll_until, + EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, is_poll_control_key, poll_interval_ms, + poll_until, }; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn gladia_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/google/provider.rs b/aimux-providers/src/google/provider.rs index 1489abbf..50d8e477 100644 --- a/aimux-providers/src/google/provider.rs +++ b/aimux-providers/src/google/provider.rs @@ -30,7 +30,8 @@ use super::options::{self, Namespace}; use super::speech_model::GoogleSpeechModel; use super::transcription::GoogleTranscriptionModel; use super::video_model::GoogleVideoModel; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders, SupportedUrlsSource}; +use crate::shared::{EndpointConfig, SupportedUrlsSource}; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; pub(crate) const DEFAULT_BASE_URL: &str = "https://generativelanguage.googleapis.com/v1beta"; pub(crate) const API_KEY_ENV_VAR: &str = "GOOGLE_GENERATIVE_AI_API_KEY"; diff --git a/aimux-providers/src/google/tests/language_model.rs b/aimux-providers/src/google/tests/language_model.rs index a04483b2..47d6bfc0 100644 --- a/aimux-providers/src/google/tests/language_model.rs +++ b/aimux-providers/src/google/tests/language_model.rs @@ -20,8 +20,9 @@ use serde_json::{Value, json}; use crate::google::api_types::Candidate; use crate::google::options::Namespace; use crate::google::{GoogleModel, GoogleProvider, GoogleProviderSettings, create_google}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, EndpointHeaders, ProviderHeaders}; +use crate::shared::{EndpointConfig, EndpointHeaders}; use crate::test_support::mock_fetch::{Canned, MockFetch, Seen}; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; // ── doGenerate helpers ─────────────────────────────────────────────────────── diff --git a/aimux-providers/src/google_pse.rs b/aimux-providers/src/google_pse.rs index 926df119..e75d4d6d 100644 --- a/aimux-providers/src/google_pse.rs +++ b/aimux-providers/src/google_pse.rs @@ -28,7 +28,8 @@ use aimux_core::search_model::{ use aimux_core::shared::SharedProviderOptions; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Fixed model ID for the Google PSE search model. const MODEL_ID: &str = "google-pse-search"; diff --git a/aimux-providers/src/groq/provider.rs b/aimux-providers/src/groq/provider.rs index 016e2f29..1ac98ad8 100644 --- a/aimux-providers/src/groq/provider.rs +++ b/aimux-providers/src/groq/provider.rs @@ -14,7 +14,8 @@ use aimux_core::provider::{Provider, ProviderDiscovery}; use aimux_core::transcription_model::TranscriptionModel; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; use super::{GroqChatLanguageModel, GroqTranscriptionModel}; diff --git a/aimux-providers/src/huggingface/provider.rs b/aimux-providers/src/huggingface/provider.rs index 2667fbba..cb4cbd90 100644 --- a/aimux-providers/src/huggingface/provider.rs +++ b/aimux-providers/src/huggingface/provider.rs @@ -14,8 +14,9 @@ use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_bas use super::responses::HuggingFaceResponsesModel; use crate::openai::config::OpenAIModelConfig; +use crate::shared::EndpointConfig; use crate::shared::TransformRequestBody; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use aimux_provider_utils::{Credential, ProviderHeaders}; pub(crate) const DEFAULT_BASE_URL: &str = "https://router.huggingface.co/v1"; pub(crate) const API_KEY_ENV_VAR: &str = "HUGGINGFACE_API_KEY"; diff --git a/aimux-providers/src/hume.rs b/aimux-providers/src/hume.rs index 4fe4aae1..3e727f6e 100644 --- a/aimux-providers/src/hume.rs +++ b/aimux-providers/src/hume.rs @@ -32,7 +32,8 @@ use aimux_core::speech_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Hume errors carry a top-level `message` with a `code` such as `"E0101"` /// (https://dev.hume.ai/docs/resources/errors); the nested `{error: diff --git a/aimux-providers/src/jina_ai.rs b/aimux-providers/src/jina_ai.rs index 376a1efe..4ddcd2f7 100644 --- a/aimux-providers/src/jina_ai.rs +++ b/aimux-providers/src/jina_ai.rs @@ -27,7 +27,8 @@ use aimux_core::shared::SharedProviderOptions; use aimux_core::types::Warning; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// Jina AI error response structure: `{ "detail": "...", "code": "..." }`. /// diff --git a/aimux-providers/src/klingai.rs b/aimux-providers/src/klingai.rs index b453da86..8e759a23 100644 --- a/aimux-providers/src/klingai.rs +++ b/aimux-providers/src/klingai.rs @@ -27,7 +27,8 @@ use aimux_core::video_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn klingai_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/linkup.rs b/aimux-providers/src/linkup.rs index 5f2a6f7b..503fb35a 100644 --- a/aimux-providers/src/linkup.rs +++ b/aimux-providers/src/linkup.rs @@ -26,7 +26,8 @@ use aimux_core::search_model::{ use aimux_core::shared::SharedProviderOptions; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// Fixed model ID for the Linkup search model. /// Failed-response handler of the Linkup endpoint: `message`, and `type` diff --git a/aimux-providers/src/lmnt.rs b/aimux-providers/src/lmnt.rs index cc532021..0e8806e5 100644 --- a/aimux-providers/src/lmnt.rs +++ b/aimux-providers/src/lmnt.rs @@ -30,7 +30,8 @@ use aimux_core::speech_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; fn lmnt_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/luma.rs b/aimux-providers/src/luma.rs index 9d2f596a..7cebc4a5 100644 --- a/aimux-providers/src/luma.rs +++ b/aimux-providers/src/luma.rs @@ -27,9 +27,10 @@ use aimux_provider_utils::HttpRequest; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use crate::shared::{ - Credential, EndpointConfig, POLL_INTERVAL_MILLIS_KEY, PollStep, ProviderHeaders, - is_poll_control_key, poll_interval_ms, poll_until, retry_download, + EndpointConfig, POLL_INTERVAL_MILLIS_KEY, PollStep, is_poll_control_key, poll_interval_ms, + poll_until, retry_download, }; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn luma_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/mistral/provider.rs b/aimux-providers/src/mistral/provider.rs index 0ae6d203..9927a17a 100644 --- a/aimux-providers/src/mistral/provider.rs +++ b/aimux-providers/src/mistral/provider.rs @@ -15,7 +15,8 @@ use aimux_core::speech_model::SpeechModel; use aimux_core::transcription_model::TranscriptionModel; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders, SupportedUrlsSource}; +use crate::shared::{EndpointConfig, SupportedUrlsSource}; +use aimux_provider_utils::{Credential, ProviderHeaders}; use super::error::mistral_failed_response_handler; use super::{MistralEmbeddingModel, MistralModel, MistralSpeechModel, MistralTranscriptionModel}; diff --git a/aimux-providers/src/open_responses/provider.rs b/aimux-providers/src/open_responses/provider.rs index daeb90df..690dab02 100644 --- a/aimux-providers/src/open_responses/provider.rs +++ b/aimux-providers/src/open_responses/provider.rs @@ -9,9 +9,8 @@ use aimux_core::language_model::LanguageModel; use aimux_core::provider::Provider; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{ - Credential, EndpointConfig, EndpointHeaders, ProviderHeaders, TransformRequestBody, -}; +use crate::shared::{EndpointConfig, EndpointHeaders, TransformRequestBody}; +use aimux_provider_utils::{Credential, ProviderHeaders}; use super::responses::OpenResponsesModel; diff --git a/aimux-providers/src/openai/config.rs b/aimux-providers/src/openai/config.rs index 28296b96..de8e144a 100644 --- a/aimux-providers/src/openai/config.rs +++ b/aimux-providers/src/openai/config.rs @@ -12,9 +12,9 @@ use std::collections::HashMap; use serde_json::Value; -use crate::shared::ProviderHeaders; use aimux_core::AiMuxError; use aimux_core::language_model::SupportedUrls; +use aimux_provider_utils::ProviderHeaders; use aimux_provider_utils::{ ExchangeContext, FetchFunction, HeaderMapOpt, HttpRequest, Resolvable, combine_headers, normalize_headers, diff --git a/aimux-providers/src/openai/provider.rs b/aimux-providers/src/openai/provider.rs index 01428f63..cf1c096e 100644 --- a/aimux-providers/src/openai/provider.rs +++ b/aimux-providers/src/openai/provider.rs @@ -29,7 +29,7 @@ use crate::openai::image::image_model::OpenAIImageModel; use crate::openai::responses::responses_language_model::OpenAIResponsesModel; use crate::openai::speech::speech_model::OpenAISpeechModel; use crate::openai::transcription::transcription_model::OpenAITranscriptionModel; -use crate::shared::{Credential, ProviderHeaders}; +use aimux_provider_utils::{Credential, ProviderHeaders}; pub(crate) const DEFAULT_BASE_URL: &str = "https://api.openai.com/v1"; diff --git a/aimux-providers/src/openai/tests/responses/convert_to_responses_input.rs b/aimux-providers/src/openai/tests/responses/convert_to_responses_input.rs index 0bdbb4fa..9f0d6f40 100644 --- a/aimux-providers/src/openai/tests/responses/convert_to_responses_input.rs +++ b/aimux-providers/src/openai/tests/responses/convert_to_responses_input.rs @@ -9,8 +9,8 @@ use aimux_provider_utils::Resolvable; use crate::openai::config::OpenAIModelConfig; use crate::openai::responses::{OpenAIResponsesModel, ResponsesNamespace, ResponsesProfile}; -use crate::shared::{Credential, ProviderHeaders}; use crate::test_support::mock_fetch::{Canned, MockFetch}; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// What upstream's `convertToOpenAIResponsesInput` returns. #[derive(Debug)] diff --git a/aimux-providers/src/openai/tests/responses/responses_language_model.rs b/aimux-providers/src/openai/tests/responses/responses_language_model.rs index 2ebdda39..40788d77 100644 --- a/aimux-providers/src/openai/tests/responses/responses_language_model.rs +++ b/aimux-providers/src/openai/tests/responses/responses_language_model.rs @@ -15,10 +15,10 @@ use aimux_core::result::GenerateResult; use crate::openai::config::OpenAIModelConfig; use crate::openai::responses::OpenAIResponsesModel; use crate::openai::{OpenAIProvider, OpenAIProviderSettings, create_openai}; -use crate::shared::Credential; -use crate::shared::ProviderHeaders; use crate::test_support::mock_fetch::{Canned, MockFetch}; use aimux_core::result::StreamResult; +use aimux_provider_utils::Credential; +use aimux_provider_utils::ProviderHeaders; use aimux_provider_utils::Resolvable; const GENERATE_BASE_URL: &str = "https://api.openai.com/v1"; diff --git a/aimux-providers/src/openai_compatible/provider.rs b/aimux-providers/src/openai_compatible/provider.rs index 2f312ef4..b92fb372 100644 --- a/aimux-providers/src/openai_compatible/provider.rs +++ b/aimux-providers/src/openai_compatible/provider.rs @@ -29,7 +29,7 @@ use serde_json::Value; use crate::preset::PresetDescriptor; use crate::shared::aisdk_pins::Package; -use crate::shared::{AuthScheme, Credential, ProviderHeaders}; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; use super::chat::{ChatSettings, ConvertUsage, MetadataExtractor, SupportedUrlsFn}; use super::{ diff --git a/aimux-providers/src/openai_compatible/tests/chat/chat_language_model.rs b/aimux-providers/src/openai_compatible/tests/chat/chat_language_model.rs index d8cc05f7..4c18b9ca 100644 --- a/aimux-providers/src/openai_compatible/tests/chat/chat_language_model.rs +++ b/aimux-providers/src/openai_compatible/tests/chat/chat_language_model.rs @@ -18,8 +18,8 @@ use crate::openai_compatible::{ MetadataExtractor, OpenAICompatibleChatModel, OpenAICompatibleProvider, OpenAICompatibleProviderSettings, StreamMetadataExtractor, create_openai_compatible, }; -use crate::shared::{AuthScheme, Credential, ProviderHeaders}; use crate::test_support::mock_fetch::{Canned, MockFetch, Seen}; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// The user agent aimux pins (upstream's test server sees `0.0.0-test` /// versions and its header snapshots leave the header out). @@ -4610,7 +4610,7 @@ mod error_structure_and_base_url { use aimux_provider_utils::ProviderErrorParts; use crate::openai_compatible::{Assembly, BaseUrl, ChatSettings, OpenAICompatibleProvider}; - use crate::shared::Credential; + use aimux_provider_utils::Credential; fn hello() -> CallOptions { CallOptions::new(vec![LanguageModelMessage::user_text("hi")]) diff --git a/aimux-providers/src/openai_compatible/tests/completion/completion_language_model.rs b/aimux-providers/src/openai_compatible/tests/completion/completion_language_model.rs index 744854ce..40952248 100644 --- a/aimux-providers/src/openai_compatible/tests/completion/completion_language_model.rs +++ b/aimux-providers/src/openai_compatible/tests/completion/completion_language_model.rs @@ -19,8 +19,8 @@ use crate::openai_compatible::{BaseUrl, ChatSettings, CompatModelConfig}; use crate::openai_compatible::{ OpenAICompatibleProvider, OpenAICompatibleProviderSettings, create_openai_compatible, }; -use crate::shared::{Credential, ProviderHeaders}; use crate::test_support::mock_fetch::{Canned, MockFetch, Seen}; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// The user agent aimux pins (upstream mocks `0.0.0-test`); compared apart /// from the other request headers, which upstream's test server lists diff --git a/aimux-providers/src/openai_compatible/tests/image/image_model.rs b/aimux-providers/src/openai_compatible/tests/image/image_model.rs index ec1131fc..7b4b6d85 100644 --- a/aimux-providers/src/openai_compatible/tests/image/image_model.rs +++ b/aimux-providers/src/openai_compatible/tests/image/image_model.rs @@ -17,8 +17,8 @@ use aimux_provider_utils::{HeaderMapOpt, ProviderErrorParts}; use crate::openai_compatible::OpenAICompatibleImageModel; use crate::openai_compatible::error::ErrorStructure; use crate::openai_compatible::{BaseUrl, ChatSettings, CompatModelConfig}; -use crate::shared::{Credential, ProviderHeaders}; use crate::test_support::mock_fetch::{Canned, RouteFetch, Seen}; +use aimux_provider_utils::{Credential, ProviderHeaders}; const PROMPT: &str = "A photorealistic astronaut riding a horse"; diff --git a/aimux-providers/src/parallel_ai.rs b/aimux-providers/src/parallel_ai.rs index 25a05363..8bad902e 100644 --- a/aimux-providers/src/parallel_ai.rs +++ b/aimux-providers/src/parallel_ai.rs @@ -25,7 +25,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Fixed model ID for the Parallel AI search model. /// Failed-response handler of the Parallel AI endpoint: `message`, and `type` diff --git a/aimux-providers/src/preset.rs b/aimux-providers/src/preset.rs index edf85d70..0880d12d 100644 --- a/aimux-providers/src/preset.rs +++ b/aimux-providers/src/preset.rs @@ -40,7 +40,7 @@ use crate::openai_compatible::chat::usage_from_raw; use crate::openai_compatible::{ Assembly, BaseUrl, ChatSettings, ConvertUsage, OpenAICompatibleProvider, TransformRequestBody, }; -use crate::shared::Credential; +use aimux_provider_utils::Credential; /// How a preset authenticates. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -458,7 +458,7 @@ fn assemble( let (package, fetch) = match descriptor.user_agent.0 { "amazon-bedrock" => { let package = crate::shared::aisdk_pins::AMAZON_BEDROCK; - let fetch: FetchFunction = Arc::new(crate::shared::UserAgentFetch { + let fetch: FetchFunction = Arc::new(aimux_provider_utils::UserAgentFetch { inner: settings.fetch, provider_utils: None, suffix: Some(format!("ai-sdk-{}/{}", package.name, package.version)), diff --git a/aimux-providers/src/prodia.rs b/aimux-providers/src/prodia.rs index 178bfd13..c9b32729 100644 --- a/aimux-providers/src/prodia.rs +++ b/aimux-providers/src/prodia.rs @@ -23,7 +23,8 @@ use aimux_core::image_model::{ use aimux_core::shared::Warning; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; fn prodia_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/recraft.rs b/aimux-providers/src/recraft.rs index 9f118657..5fc1a604 100644 --- a/aimux-providers/src/recraft.rs +++ b/aimux-providers/src/recraft.rs @@ -28,10 +28,8 @@ use aimux_core::shared::Warning; use aimux_provider_utils::HttpRequest; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{ - Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, ProviderHeaders, poll_interval_ms, - retry_download, -}; +use crate::shared::{EndpointConfig, POLL_INTERVAL_MS_KEY, poll_interval_ms, retry_download}; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn recraft_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/replicate.rs b/aimux-providers/src/replicate.rs index 18656aef..03fadb50 100644 --- a/aimux-providers/src/replicate.rs +++ b/aimux-providers/src/replicate.rs @@ -24,9 +24,9 @@ use aimux_provider_utils::HttpRequest; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use crate::shared::{ - AuthScheme, Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, ProviderHeaders, - is_poll_control_key, poll_interval_ms, retry_download, + EndpointConfig, POLL_INTERVAL_MS_KEY, is_poll_control_key, poll_interval_ms, retry_download, }; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Replicate's `prefer: wait` holds the connection for at most this long when /// no explicit duration is given (their documented default and maximum). diff --git a/aimux-providers/src/revai.rs b/aimux-providers/src/revai.rs index 769750f6..44c60345 100644 --- a/aimux-providers/src/revai.rs +++ b/aimux-providers/src/revai.rs @@ -32,9 +32,9 @@ use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_bas use aimux_provider_utils::{MultipartForm, media_type_to_extension}; use crate::shared::{ - Credential, EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, ProviderHeaders, poll_interval_ms, - poll_until, retry_download, + EndpointConfig, POLL_INTERVAL_MS_KEY, PollStep, poll_interval_ms, poll_until, retry_download, }; +use aimux_provider_utils::{Credential, ProviderHeaders}; fn revai_failed_response_handler() -> aimux_provider_utils::ResponseHandler { aimux_provider_utils::create_json_error_response_handler(|data| { diff --git a/aimux-providers/src/runwayml.rs b/aimux-providers/src/runwayml.rs index c960a24e..84b9f8d4 100644 --- a/aimux-providers/src/runwayml.rs +++ b/aimux-providers/src/runwayml.rs @@ -27,7 +27,8 @@ use aimux_core::video_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; const PROVIDER_NAME: &str = "runwayml"; const RUNWAY_VERSION: &str = "2024-11-06"; diff --git a/aimux-providers/src/searxng.rs b/aimux-providers/src/searxng.rs index cb208a3d..48337c74 100644 --- a/aimux-providers/src/searxng.rs +++ b/aimux-providers/src/searxng.rs @@ -28,7 +28,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{BaseUrl, Credential, EndpointConfig, HttpEndpoint, ProviderHeaders}; +use crate::shared::{BaseUrl, EndpointConfig, HttpEndpoint}; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// Fixed model ID for the SearXNG search model. const MODEL_ID: &str = "searxng-search"; diff --git a/aimux-providers/src/serper.rs b/aimux-providers/src/serper.rs index 2f02764b..05b50cec 100644 --- a/aimux-providers/src/serper.rs +++ b/aimux-providers/src/serper.rs @@ -21,7 +21,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Failed-response handler of the Serper endpoint: `message`, and `type` /// else `code` as the provider code, from the body's `error` object, or from diff --git a/aimux-providers/src/shared/exchange.rs b/aimux-providers/src/shared/exchange.rs index ed73e5b2..2e616f09 100644 --- a/aimux-providers/src/shared/exchange.rs +++ b/aimux-providers/src/shared/exchange.rs @@ -22,7 +22,8 @@ use aimux_provider_utils::{ load_setting, normalize_headers, validate_base_url, }; -use super::{Credential, ProviderHeaders, TransformRequestBody}; +use super::TransformRequestBody; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// What one request is addressed to: the base URL and the provider headers, /// both resolved for this request. diff --git a/aimux-providers/src/shared/mod.rs b/aimux-providers/src/shared/mod.rs index 3158f5cc..a50b8246 100644 --- a/aimux-providers/src/shared/mod.rs +++ b/aimux-providers/src/shared/mod.rs @@ -25,10 +25,7 @@ use std::sync::Arc; use serde_json::Value; use aimux_core::AiMuxError; -use aimux_provider_utils::{ - FetchFunction, HeaderMapOpt, Resolvable, combine_headers, load_api_key, normalize_headers, - with_user_agent_suffix, -}; +use aimux_provider_utils::{FetchFunction, UserAgentFetch}; /// A host part (a Vertex location, an Azure resource name) is one DNS label: letters, digits and hyphens, not starting or /// ending with a hyphen (`isValidHostnamePart`). @@ -76,143 +73,6 @@ pub(crate) fn base_url_setting( /// body is serialized and before it is sent. pub type TransformRequestBody = Arc Value + Send + Sync>; -/// Where the credential of a provider comes from. -#[derive(Clone, Debug)] -pub(crate) enum Credential { - /// No credential: no `Authorization` header is produced and no key is - /// resolved (a local server, or `auth: none` in a preset). - None, - /// The caller's key. It is used as given, `""` included, and never falls - /// back to the environment. A [`Resolvable::Future`] is awaited once, an - /// `AsyncFn` on every request. - Explicit(Resolvable), - /// Read the environment variable on every request; unset fails that - /// request with `AiMuxError::LoadApiKey`. - Env { var: String, description: String }, - /// Read the environment variable on every request; unset or empty sends - /// no credential (a local runtime started with or without a key). - OptionalEnv(String), -} - -impl Credential { - /// `Explicit` when the caller gave a key, otherwise `Env`. - pub(crate) fn explicit_or_env( - api_key: Option>, - var: &str, - description: &str, - ) -> Self { - match api_key { - Some(key) => Self::Explicit(key), - None => Self::Env { - var: var.to_string(), - description: description.to_string(), - }, - } - } - - /// The secret of this request, `None` for [`Credential::None`]. A - /// package that puts the key somewhere other than a header (a query - /// parameter, a JWT claim) asks for it here, per request. - pub(crate) async fn secret(&self) -> Result, AiMuxError> { - match self { - Self::None => Ok(None), - Self::Explicit(key) => Ok(Some(key.resolve().await?)), - Self::Env { var, description } => Ok(Some(load_api_key(None, var, description)?)), - Self::OptionalEnv(var) => Ok(std::env::var(var).ok().filter(|key| !key.is_empty())), - } - } -} - -/// How the credential is put on the wire. -#[derive(Clone, Copy, Debug)] -pub(crate) enum AuthScheme { - /// `Authorization: Bearer `. - Bearer, - /// `: ` (`x-api-key`). - Header(&'static str), - /// `Authorization: ` (`Token `, `Key `). - Scheme(&'static str), -} - -/// The provider-level request headers, as data. They are produced on every -/// request: the credential first, then the fixed headers (organization, -/// project, version, ...), then the user's headers, which may override or -/// remove any of them (case-insensitively, `None` removes), and finally the -/// `ai-sdk-{package}/{version}` user-agent suffix when one is set. -#[derive(Clone, Debug)] -pub(crate) struct ProviderHeaders { - pub credential: Credential, - pub scheme: AuthScheme, - pub fixed: Vec<(String, String)>, - pub user: Option, - /// `(package, version)` of the user-agent suffix. - pub user_agent: Option<(&'static str, &'static str)>, -} - -impl ProviderHeaders { - /// Headers with `credential` put on the wire by `scheme`, no user-agent - /// suffix. - pub(crate) fn new( - credential: Credential, - scheme: AuthScheme, - fixed: Vec<(String, String)>, - user: Option, - ) -> Self { - Self { - credential, - scheme, - fixed, - user, - user_agent: None, - } - } - - /// Headers with a bearer credential, no user-agent suffix. - pub(crate) fn bearer( - credential: Credential, - fixed: Vec<(String, String)>, - user: Option, - ) -> Self { - Self::new(credential, AuthScheme::Bearer, fixed, user) - } - - /// The same headers with the `ai-sdk-{package}/{version}` user-agent - /// suffix. - #[must_use] - pub(crate) fn with_user_agent(mut self, package: &'static str, version: &'static str) -> Self { - self.user_agent = Some((package, version)); - self - } - - pub(crate) async fn resolve(&self) -> Result { - let mut layer = HeaderMapOpt::new(); - if let Some(secret) = self.credential.secret().await? { - let (name, value) = match self.scheme { - AuthScheme::Bearer => ("Authorization", format!("Bearer {secret}")), - AuthScheme::Header(name) => (name, secret), - AuthScheme::Scheme(scheme) => ("Authorization", format!("{scheme} {secret}")), - }; - layer.insert(name.to_string(), Some(value)); - } - for (name, value) in &self.fixed { - layer.insert(name.clone(), Some(value.clone())); - } - let headers = match &self.user { - Some(user) => combine_headers(&[&layer, user]), - None => layer, - }; - let Some((package, version)) = self.user_agent else { - return Ok(headers); - }; - let mut headers = normalize_headers(headers).into_iter().collect(); - with_user_agent_suffix(&mut headers, &format!("ai-sdk-{package}/{version}")); - Ok(headers - .into_iter() - .map(|(name, value)| (name, Some(value))) - .collect()) - } -} - /// The transport of a pinned package: `fetch` (the process default, resolved /// per request, when `None`) behind a [`UserAgentFetch`] that names the /// provider-utils `package` depends on in the `ai-sdk-provider-utils/` part @@ -230,73 +90,3 @@ pub(crate) fn package_fetch( suffix: None, }) } - -/// A transport decorator that edits the request's `user-agent` just before -/// forwarding it, after the helper layer has added -/// `ai-sdk-provider-utils/`: it names `provider_utils` in that part (when -/// set) and appends `suffix` (when set). The suffix is the AI SDK AWS -/// transports' own `withUserAgentSuffix` (amazon-bedrock-sigv4-fetch.ts:42-46 -/// and :126-130, anthropic-aws-fetch.ts:42-46 and :126-130); the runtime -/// fragment they append is left out, as everywhere in aimux. -pub(crate) struct UserAgentFetch { - /// `None` is the process default transport, resolved per request. - pub inner: Option, - pub provider_utils: Option<&'static str>, - pub suffix: Option, -} - -#[async_trait::async_trait] -impl aimux_provider_utils::Fetch for UserAgentFetch { - async fn fetch( - &self, - mut request: aimux_provider_utils::FetchRequest, - ) -> Result { - transport_user_agent( - &mut request.headers, - self.provider_utils, - self.suffix.as_deref(), - ); - let inner = self - .inner - .clone() - .unwrap_or_else(aimux_provider_utils::default_fetch); - inner.fetch(request).await - } -} - -/// Rewrite the last `ai-sdk-provider-utils/` part of the `user-agent` to -/// `provider_utils` (when set), then append `suffix` (when set, creating the -/// header if absent). A value that is not a valid header is left as it was. -pub(crate) fn transport_user_agent( - headers: &mut reqwest::header::HeaderMap, - provider_utils: Option<&str>, - suffix: Option<&str>, -) { - let current = headers - .get(reqwest::header::USER_AGENT) - .and_then(|value| value.to_str().ok()) - .unwrap_or_default(); - let mut parts: Vec = current - .split(' ') - .filter(|part| !part.is_empty()) - .map(String::from) - .collect(); - let last_utils = parts - .iter_mut() - .rev() - .find(|part| part.starts_with("ai-sdk-provider-utils/")); - match (provider_utils, last_utils) { - (Some(version), Some(part)) => *part = format!("ai-sdk-provider-utils/{version}"), - // Nothing to rename or append: the header stays exactly as it is. - _ if suffix.is_none() => return, - _ => {} - } - parts.extend(suffix.map(String::from)); - let user_agent = parts.join(" "); - if user_agent == current { - return; - } - if let Ok(value) = reqwest::header::HeaderValue::try_from(user_agent) { - headers.insert(reqwest::header::USER_AGENT, value); - } -} diff --git a/aimux-providers/src/stability.rs b/aimux-providers/src/stability.rs index 8f2a8636..414df9cc 100644 --- a/aimux-providers/src/stability.rs +++ b/aimux-providers/src/stability.rs @@ -29,7 +29,8 @@ use aimux_core::shared::Warning; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; use aimux_provider_utils::{HttpBody, MultipartForm}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; /// Stability error response structure: `{ "id": "...", "name": "...", "errors": ["..."] }`. /// diff --git a/aimux-providers/src/tavily.rs b/aimux-providers/src/tavily.rs index 8bbdc9fd..7ab9b4f5 100644 --- a/aimux-providers/src/tavily.rs +++ b/aimux-providers/src/tavily.rs @@ -20,7 +20,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; const MODEL_ID: &str = "tavily-search"; diff --git a/aimux-providers/src/tinyfish.rs b/aimux-providers/src/tinyfish.rs index 17e0a661..9df2adff 100644 --- a/aimux-providers/src/tinyfish.rs +++ b/aimux-providers/src/tinyfish.rs @@ -25,7 +25,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Fixed model id for the TinyFish search model. /// Failed-response handler of the TinyFish endpoint: `message`, and `type` diff --git a/aimux-providers/src/vertex/anthropic/anthropic_provider.rs b/aimux-providers/src/vertex/anthropic/anthropic_provider.rs index 8f101f78..29d1867b 100644 --- a/aimux-providers/src/vertex/anthropic/anthropic_provider.rs +++ b/aimux-providers/src/vertex/anthropic/anthropic_provider.rs @@ -23,7 +23,6 @@ use crate::anthropic::config::{ AnthropicEndpoint, AnthropicModelConfig, AnthropicModelHooks, MessagesUrl, TransformRequestBody, }; use crate::anthropic::options::options_name_of; -use crate::shared::{Credential, ProviderHeaders}; use crate::vertex::auth_google_auth_library::GoogleAuth; use crate::vertex::provider_base::Resolver; use crate::vertex::{ @@ -34,6 +33,7 @@ use aimux_core::embedding_model::EmbeddingModel; use aimux_core::image_model::ImageModel; use aimux_core::language_model::{LanguageModel, SupportedUrls}; use aimux_core::provider::Provider; +use aimux_provider_utils::{Credential, ProviderHeaders}; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable}; use serde_json::{Value, json}; use std::sync::{Arc, OnceLock}; diff --git a/aimux-providers/src/vertex/maas/maas_provider.rs b/aimux-providers/src/vertex/maas/maas_provider.rs index 7bf2b452..0b34f92b 100644 --- a/aimux-providers/src/vertex/maas/maas_provider.rs +++ b/aimux-providers/src/vertex/maas/maas_provider.rs @@ -46,9 +46,9 @@ pub fn create_google_vertex_maas( ..Default::default() }, base_url, - crate::shared::ProviderHeaders::new( - crate::shared::Credential::None, - crate::shared::AuthScheme::Bearer, + aimux_provider_utils::ProviderHeaders::new( + aimux_provider_utils::Credential::None, + aimux_provider_utils::AuthScheme::Bearer, Vec::new(), None, ), diff --git a/aimux-providers/src/vertex/xai/xai_provider.rs b/aimux-providers/src/vertex/xai/xai_provider.rs index 954e156a..3cde4c2e 100644 --- a/aimux-providers/src/vertex/xai/xai_provider.rs +++ b/aimux-providers/src/vertex/xai/xai_provider.rs @@ -86,9 +86,9 @@ pub fn create_google_vertex_xai( ..Default::default() }, base_url, - crate::shared::ProviderHeaders::new( - crate::shared::Credential::None, - crate::shared::AuthScheme::Bearer, + aimux_provider_utils::ProviderHeaders::new( + aimux_provider_utils::Credential::None, + aimux_provider_utils::AuthScheme::Bearer, Vec::new(), None, ), diff --git a/aimux-providers/src/voyage/mod.rs b/aimux-providers/src/voyage/mod.rs index be1f9cf9..7d092a4a 100644 --- a/aimux-providers/src/voyage/mod.rs +++ b/aimux-providers/src/voyage/mod.rs @@ -28,7 +28,8 @@ use aimux_core::provider::Provider; use aimux_core::reranking_model::RerankingModel; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; pub(crate) fn voyage_failed_response_handler() -> aimux_provider_utils::ResponseHandler { diff --git a/aimux-providers/src/xai/provider.rs b/aimux-providers/src/xai/provider.rs index c3636fac..132e5df7 100644 --- a/aimux-providers/src/xai/provider.rs +++ b/aimux-providers/src/xai/provider.rs @@ -14,7 +14,8 @@ use aimux_core::video_model::VideoModel; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable}; use futures::future::BoxFuture; -use crate::shared::{Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{Credential, ProviderHeaders}; use super::error::xai_failed_response_handler; use super::{XaiResponsesModel, XaiSpeechModel, XaiTranscriptionModel, XaiVideoModel}; diff --git a/aimux-providers/src/you_com.rs b/aimux-providers/src/you_com.rs index 44aefa58..cb9201df 100644 --- a/aimux-providers/src/you_com.rs +++ b/aimux-providers/src/you_com.rs @@ -26,7 +26,8 @@ use aimux_core::search_model::{ }; use aimux_provider_utils::{FetchFunction, HeaderMapOpt, Resolvable, validate_base_url}; -use crate::shared::{AuthScheme, Credential, EndpointConfig, ProviderHeaders}; +use crate::shared::EndpointConfig; +use aimux_provider_utils::{AuthScheme, Credential, ProviderHeaders}; /// Fixed model id for the You.com search model. /// Failed-response handler of the You.com endpoint: `message`, and `type` From f257d4c2c7e912369d9731a322740f878fd40c3e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 00:31:07 +0000 Subject: [PATCH 3/3] docs(changelog): list the provider headers and user-agent fetch Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0116HPMoqaLfFr1TyqDq1hrW --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a96f62..9e2af593 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1417,6 +1417,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 xAI, Anthropic, Google and DeepSeek converters resolve file references with the second. A missing DeepSeek reference now names the providers the reference has, as the AI SDK's `NoSuchProviderReferenceError` does. +- `aimux_provider_utils::{Credential, AuthScheme, ProviderHeaders}`, the + provider-level request headers as data (credential, fixed headers, user + headers, `ai-sdk-/` user-agent suffix), and + `aimux_provider_utils::{UserAgentFetch, transport_user_agent}`, the + user-agent edits a transport makes (RFC-0046 R1). They move out of + `aimux-providers`, whose providers build their headers with them unchanged. ### Fixed