1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use crate::{
5 credentials::AuthState, ModelRequest, ProviderRegistry, ResolveError, ResolvedModel,
6 RuntimeConfig, ValueSource,
7};
8
9#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
11pub struct ModelIdentity {
12 pub provider: String,
13 pub api: rpi_ai::Api,
14 pub model: String,
15}
16
17#[derive(Clone, Debug, serde::Serialize)]
18#[serde(rename_all = "camelCase")]
19pub struct ModelSelection {
20 pub identity: ModelIdentity,
21 pub requested_thinking: rpi_ai::ThinkingLevel,
22 pub effective_thinking: rpi_ai::ThinkingLevel,
25}
26
27impl ResolvedModel {
28 pub fn selection(&self) -> ModelSelection {
29 ModelSelection {
30 identity: ModelIdentity {
31 provider: self.model.provider.clone(),
32 api: self.model.api.clone(),
33 model: self.model.id.clone(),
34 },
35 requested_thinking: self.thinking_level,
36 effective_thinking: if self.model.reasoning {
37 self.model.clamp_thinking_level(self.thinking_level)
38 } else {
39 rpi_ai::ThinkingLevel::Off
40 },
41 }
42 }
43}
44
45#[derive(Clone, Debug, serde::Serialize)]
47pub struct RuntimeSnapshot {
48 pub revision: u64,
49 pub selection: ModelSelection,
50 pub models: Vec<rpi_ai::Model>,
51 pub auth: BTreeMap<String, AuthState>,
52 pub warnings: Vec<String>,
53}
54
55pub struct Runtime {
58 current: Arc<ResolvedModel>,
59 changes: tokio::sync::watch::Sender<Arc<RuntimeSnapshot>>,
60}
61
62impl Runtime {
63 pub fn new(
64 config: &RuntimeConfig,
65 providers: &ProviderRegistry,
66 values: &dyn ValueSource,
67 request: &ModelRequest<'_>,
68 ) -> Result<Self, ResolveError> {
69 let (current, snapshot) = prepare(config, providers, values, request, 0)?;
70 let (changes, _) = tokio::sync::watch::channel(Arc::new(snapshot));
71 Ok(Self {
72 current: Arc::new(current),
73 changes,
74 })
75 }
76
77 pub fn current(&self) -> Arc<ResolvedModel> {
79 self.current.clone()
80 }
81
82 pub fn snapshot(&self) -> Arc<RuntimeSnapshot> {
83 self.changes.borrow().clone()
84 }
85
86 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<Arc<RuntimeSnapshot>> {
87 self.changes.subscribe()
88 }
89
90 pub fn reload(
93 &mut self,
94 config: &RuntimeConfig,
95 providers: &ProviderRegistry,
96 values: &dyn ValueSource,
97 request: &ModelRequest<'_>,
98 ) -> Result<(), ResolveError> {
99 let revision = self.snapshot().revision.saturating_add(1);
100 let (current, snapshot) = prepare(config, providers, values, request, revision)?;
101 self.current = Arc::new(current);
102 self.changes.send_replace(Arc::new(snapshot));
103 Ok(())
104 }
105}
106
107fn prepare(
108 config: &RuntimeConfig,
109 providers: &ProviderRegistry,
110 values: &dyn ValueSource,
111 request: &ModelRequest<'_>,
112 revision: u64,
113) -> Result<(ResolvedModel, RuntimeSnapshot), ResolveError> {
114 let current = config.resolve(request, providers, values)?;
115 let snapshot = RuntimeSnapshot {
116 revision,
117 selection: current.selection(),
118 models: current.snapshot_catalog.clone(),
119 auth: current.snapshot_auth.clone(),
120 warnings: current.compat_warnings.clone(),
121 };
122 Ok((current, snapshot))
123}