use serde_json::{Map, Value};
use systemprompt_models::services::{Hosting, WireProtocol};
use systemprompt_models::wire::upstream::UpstreamDialect;
use systemprompt_security::credential::{AuthHeader, AuthScheme};
#[derive(Debug, Clone)]
pub struct UpstreamCall {
hosting: Hosting,
endpoint: String,
auth: AuthHeader,
extra_headers: Vec<(String, String)>,
}
impl UpstreamCall {
#[must_use]
pub const fn new(
hosting: Hosting,
endpoint: String,
auth: AuthHeader,
extra_headers: Vec<(String, String)>,
) -> Self {
Self {
hosting,
endpoint,
auth,
extra_headers,
}
}
#[must_use]
pub fn api_key(endpoint: impl Into<String>, key: impl Into<String>) -> Self {
let endpoint = endpoint.into();
Self::new(
Hosting::of(&endpoint),
endpoint,
AuthHeader {
scheme: AuthScheme::ApiKey,
value: key.into(),
},
Vec::new(),
)
}
#[must_use]
pub fn bearer(endpoint: impl Into<String>, token: impl Into<String>) -> Self {
let endpoint = endpoint.into();
Self::new(
Hosting::of(&endpoint),
endpoint,
AuthHeader {
scheme: AuthScheme::Bearer,
value: token.into(),
},
Vec::new(),
)
}
#[must_use]
pub const fn hosting(&self) -> Hosting {
self.hosting
}
#[must_use]
pub fn endpoint(&self) -> &str {
&self.endpoint
}
#[must_use]
pub const fn is_bearer(&self) -> bool {
self.auth.is_bearer()
}
#[must_use]
pub const fn dialect(&self, wire: WireProtocol) -> UpstreamDialect {
UpstreamDialect::new(wire, self.hosting)
}
#[must_use]
pub fn url(&self, wire: WireProtocol, upstream_model: &str, stream: bool) -> String {
self.dialect(wire)
.url(&self.endpoint, upstream_model, stream)
}
#[must_use]
pub fn auth_header(&self, wire: WireProtocol) -> (String, String) {
match (self.auth.scheme, self.dialect(wire).api_key_header()) {
(AuthScheme::ApiKey, Some(header)) => (header.to_owned(), self.auth.value.clone()),
_ => (
"authorization".to_owned(),
format!("Bearer {}", self.auth.value),
),
}
}
#[must_use]
pub fn headers(&self, wire: WireProtocol) -> Vec<(String, String)> {
self.headers_forwarding(wire, &[])
}
#[must_use]
pub fn headers_forwarding(
&self,
wire: WireProtocol,
forward: &[(String, String)],
) -> Vec<(String, String)> {
let dialect = self.dialect(wire);
let mut headers = vec![
self.auth_header(wire),
("content-type".to_owned(), "application/json".to_owned()),
];
headers.extend(
dialect
.required_headers()
.into_iter()
.filter(|(name, _)| {
!forward
.iter()
.any(|(sent, _)| sent.eq_ignore_ascii_case(name))
})
.map(|(name, value)| (name.to_owned(), value.to_owned())),
);
headers.extend(
forward
.iter()
.filter(|(name, _)| !dialect.drops_forwarded_header(name))
.cloned(),
);
headers.extend(self.extra_headers.iter().cloned());
headers
}
pub fn finish_body(&self, wire: WireProtocol, body: &mut Map<String, Value>) {
self.dialect(wire).finish_body(body);
}
pub fn finish_value(&self, wire: WireProtocol, body: &mut Value) {
self.dialect(wire).finish_value(body);
}
}