use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use super::{Catalog, ModelSpec};
use crate::http_client::DynHttpClient;
use crate::operation::Completion;
use crate::providers::registry::{ConnectError, ConnectOptions, ProviderId};
use crate::serve::ErasedHandler;
use crate::serve::adapters::ModelAdapter;
use crate::wasm_compat::{WasmCompatSend, WasmCompatSync};
pub trait SignIn: WasmCompatSend + WasmCompatSync + 'static {
fn handler(
&self,
spec: &ModelSpec,
transport: &Transport,
) -> Option<Result<ErasedHandler, ConnectError>>;
fn plan(&self, spec: &ModelSpec) -> Option<&'static str>;
}
pub trait Provide: WasmCompatSend + WasmCompatSync + 'static {
fn handler(
&self,
spec: &ModelSpec,
catalog: &Catalog,
transport: &Transport,
) -> Result<ErasedHandler, ConnectError>;
}
impl<F> Provide for F
where
F: Fn(&ModelSpec, &Catalog, &Transport) -> Result<ErasedHandler, ConnectError>
+ WasmCompatSend
+ WasmCompatSync
+ 'static,
{
fn handler(
&self,
spec: &ModelSpec,
catalog: &Catalog,
transport: &Transport,
) -> Result<ErasedHandler, ConnectError> {
self(spec, catalog, transport)
}
}
impl Provide for ConnectOptions {
fn handler(
&self,
spec: &ModelSpec,
catalog: &Catalog,
transport: &Transport,
) -> Result<ErasedHandler, ConnectError> {
let mut options = self.clone();
options.http = match options.http {
Some(own) => Some(transport.wrap(own)),
None => transport.shared(),
};
catalog
.connect_with(spec, options)
.map(|model| adapted(spec, model))
}
}
fn adapted(spec: &ModelSpec, model: crate::driver::DynModel<Completion>) -> ErasedHandler {
ErasedHandler::new(ModelAdapter::<Completion>::new(spec.reference(), model))
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
type Wrapper = Arc<dyn Fn(DynHttpClient) -> DynHttpClient + Send + Sync>;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
type Wrapper = Arc<dyn Fn(DynHttpClient) -> DynHttpClient>;
#[derive(Clone, Default)]
pub struct Transport {
wrappers: Vec<Wrapper>,
}
impl Transport {
pub fn wrap(&self, base: DynHttpClient) -> DynHttpClient {
self.wrappers.iter().fold(base, |client, wrap| wrap(client))
}
pub fn shared(&self) -> Option<DynHttpClient> {
#[cfg(feature = "reqwest")]
{
Some(self.wrap(rig_reqwest::shared()))
}
#[cfg(not(feature = "reqwest"))]
{
None
}
}
}
impl fmt::Debug for Transport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Transport")
.field("wrappers", &self.wrappers.len())
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ModelStatus {
Usable,
NoKey(Vec<Cow<'static, str>>),
NoTools,
Unavailable(String),
}
#[derive(Clone)]
pub struct Connector {
catalog: Catalog,
providers: HashMap<&'static str, Arc<dyn Provide>>,
sign_ins: Vec<Arc<dyn SignIn>>,
transport: Transport,
}
impl Default for Connector {
fn default() -> Self {
Self::new(Catalog::builtin().clone())
}
}
impl Connector {
pub fn new(catalog: Catalog) -> Self {
Self {
catalog,
providers: HashMap::new(),
sign_ins: Vec::new(),
transport: Transport::default(),
}
}
pub fn wrap_http(
&mut self,
wrap: impl Fn(DynHttpClient) -> DynHttpClient + WasmCompatSend + WasmCompatSync + 'static,
) {
self.transport.wrappers.push(Arc::new(wrap));
}
pub fn transport(&self) -> &Transport {
&self.transport
}
pub fn add_sign_in(&mut self, sign_in: impl SignIn) {
self.sign_ins.push(Arc::new(sign_in));
}
pub fn provide(&mut self, vendor: ProviderId, provider: impl Provide) {
self.providers.insert(vendor.vendor(), Arc::new(provider));
}
pub fn is_provided(&self, vendor: ProviderId) -> bool {
self.providers.contains_key(vendor.vendor())
}
pub fn provider(&self, vendor: ProviderId) -> Option<Arc<dyn Provide>> {
self.providers.get(vendor.vendor()).cloned()
}
pub fn catalog(&self) -> &Catalog {
&self.catalog
}
pub fn catalog_mut(&mut self) -> &mut Catalog {
&mut self.catalog
}
fn signed_in(&self, spec: &ModelSpec) -> Option<Result<ErasedHandler, ConnectError>> {
self.sign_ins
.iter()
.find_map(|sign_in| sign_in.handler(spec, &self.transport))
}
pub fn connect(
&self,
reference: &str,
) -> Result<(Arc<ModelSpec>, ErasedHandler), ConnectError> {
let spec = self.catalog.resolve(reference)?.shared();
let handler = self.handler(&spec)?;
Ok((spec, handler))
}
fn handler(&self, spec: &ModelSpec) -> Result<ErasedHandler, ConnectError> {
let connected = match self.providers.get(spec.provider.vendor()) {
Some(provider) => provider.handler(spec, &self.catalog, &self.transport),
None => {
let options = match self.transport.shared() {
Some(http) => ConnectOptions::new().http(http),
None => ConnectOptions::new(),
};
self.catalog
.connect_with(spec, options)
.map(|model| adapted(spec, model))
}
};
match connected {
Err(error @ ConnectError::MissingKey { .. }) => {
self.signed_in(spec).unwrap_or(Err(error))
}
other => other,
}
}
pub fn plan(&self, spec: &ModelSpec) -> Option<&'static str> {
self.sign_ins.iter().find_map(|sign_in| sign_in.plan(spec))
}
pub fn status(&self, spec: &ModelSpec) -> ModelStatus {
if !spec.tools {
return ModelStatus::NoTools;
}
match self.handler(spec) {
Ok(_) => ModelStatus::Usable,
Err(ConnectError::MissingKey { tried, .. }) => ModelStatus::NoKey(tried),
Err(error) => ModelStatus::Unavailable(error.to_string()),
}
}
pub fn reachable(&self) -> Vec<&ModelSpec> {
let statuses = self.statuses().into_iter();
let usable = statuses.filter(|(_, status)| *status == ModelStatus::Usable);
usable.map(|(spec, _)| spec).collect()
}
pub fn statuses(&self) -> Vec<(&ModelSpec, ModelStatus)> {
let mut checked: HashMap<ProviderId, ModelStatus> = HashMap::new();
let mut statuses: Vec<(&ModelSpec, ModelStatus)> = self
.catalog
.iter()
.map(|spec| {
let status = match spec.tools {
false => ModelStatus::NoTools,
true => checked
.entry(spec.provider)
.or_insert_with(|| self.status(spec))
.clone(),
};
(spec, status)
})
.collect();
statuses.sort_by_key(|(spec, status)| match status {
ModelStatus::Usable => {
u8::from(!self.is_provided(spec.provider) && !spec.provider.requires_credential())
}
_ => 2,
});
statuses
}
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
const _: fn(&mut Connector, ProviderId) = |connector, vendor| {
use std::rc::Rc;
struct PageSignIn {
_page: Rc<()>,
}
impl SignIn for PageSignIn {
fn handler(
&self,
_: &ModelSpec,
_: &Transport,
) -> Option<Result<ErasedHandler, ConnectError>> {
None
}
fn plan(&self, _: &ModelSpec) -> Option<&'static str> {
None
}
}
let page = Rc::new(());
let wrapped = page.clone();
connector.wrap_http(move |client| {
let _ = &wrapped;
client
});
let provided = page.clone();
connector.provide(vendor, move |_: &ModelSpec, _: &Catalog, _: &Transport| {
let _ = &provided;
Err(ConnectError::Provider("not on this page".into()))
});
connector.add_sign_in(PageSignIn { _page: page });
};
#[cfg(test)]
mod tests;