use std::collections::BTreeMap;
use std::sync::Arc;
use crate::{
credentials::AuthState, ModelRequest, ProviderRegistry, ResolveError, ResolvedModel,
RuntimeConfig, ValueSource,
};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct ModelIdentity {
pub provider: String,
pub api: rpi_ai::Api,
pub model: String,
}
#[derive(Clone, Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelSelection {
pub identity: ModelIdentity,
pub requested_thinking: rpi_ai::ThinkingLevel,
pub effective_thinking: rpi_ai::ThinkingLevel,
}
impl ResolvedModel {
pub fn selection(&self) -> ModelSelection {
ModelSelection {
identity: ModelIdentity {
provider: self.model.provider.clone(),
api: self.model.api.clone(),
model: self.model.id.clone(),
},
requested_thinking: self.thinking_level,
effective_thinking: if self.model.reasoning {
self.model.clamp_thinking_level(self.thinking_level)
} else {
rpi_ai::ThinkingLevel::Off
},
}
}
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct RuntimeSnapshot {
pub revision: u64,
pub selection: ModelSelection,
pub models: Vec<rpi_ai::Model>,
pub auth: BTreeMap<String, AuthState>,
pub warnings: Vec<String>,
}
pub struct Runtime {
current: Arc<ResolvedModel>,
changes: tokio::sync::watch::Sender<Arc<RuntimeSnapshot>>,
}
impl Runtime {
pub fn new(
config: &RuntimeConfig,
providers: &ProviderRegistry,
values: &dyn ValueSource,
request: &ModelRequest<'_>,
) -> Result<Self, ResolveError> {
let (current, snapshot) = prepare(config, providers, values, request, 0)?;
let (changes, _) = tokio::sync::watch::channel(Arc::new(snapshot));
Ok(Self {
current: Arc::new(current),
changes,
})
}
pub fn current(&self) -> Arc<ResolvedModel> {
self.current.clone()
}
pub fn snapshot(&self) -> Arc<RuntimeSnapshot> {
self.changes.borrow().clone()
}
pub fn subscribe(&self) -> tokio::sync::watch::Receiver<Arc<RuntimeSnapshot>> {
self.changes.subscribe()
}
pub fn reload(
&mut self,
config: &RuntimeConfig,
providers: &ProviderRegistry,
values: &dyn ValueSource,
request: &ModelRequest<'_>,
) -> Result<(), ResolveError> {
let revision = self.snapshot().revision.saturating_add(1);
let (current, snapshot) = prepare(config, providers, values, request, revision)?;
self.current = Arc::new(current);
self.changes.send_replace(Arc::new(snapshot));
Ok(())
}
}
fn prepare(
config: &RuntimeConfig,
providers: &ProviderRegistry,
values: &dyn ValueSource,
request: &ModelRequest<'_>,
revision: u64,
) -> Result<(ResolvedModel, RuntimeSnapshot), ResolveError> {
let current = config.resolve(request, providers, values)?;
let snapshot = RuntimeSnapshot {
revision,
selection: current.selection(),
models: current.snapshot_catalog.clone(),
auth: current.snapshot_auth.clone(),
warnings: current.compat_warnings.clone(),
};
Ok((current, snapshot))
}