1use crate::{config, settings};
3use rpi_ai::{Model, ThinkingLevel};
4use rpi_runtime::provider::RuntimeFactories;
5pub use rpi_runtime::provider::{
6 authenticated_catalog, available_catalog, ResolveError, ResolvedModel, ANTHROPIC_API_KEY_ENV,
7 ANTHROPIC_AUTH_TOKEN_ENV, ANTHROPIC_BASE_URL_ENV, DEFAULT_MODEL_ID, DEFAULT_THINKING_LEVEL,
8 NO_API_KEY_HINT, OPENAI_API_KEY_ENV,
9};
10use std::sync::Arc;
11
12pub(crate) struct CliValues;
13impl rpi_runtime::ValueSource for CliValues {
14 fn env(&self, name: &str) -> Option<String> {
15 std::env::var(name).ok()
16 }
17 fn command(&self, command: &str, cached: bool) -> Option<String> {
18 if cached {
19 config::resolve_command(command)
20 } else {
21 config::resolve_command_uncached(command)
22 }
23 }
24}
25pub(crate) fn declaration(
26 manifest: &rpi_extensions::ProviderManifest,
27) -> rpi_runtime::ProviderDeclaration {
28 rpi_runtime::ProviderDeclaration {
29 id: manifest.id.clone(),
30 models: manifest.models.clone(),
31 api_key_env: manifest.api_key_env.clone(),
32 requires_auth: manifest.requires_auth,
33 }
34}
35fn runtime_settings(settings: settings::Settings) -> rpi_runtime::RuntimeSettings {
36 let timeout = settings.http_idle_timeout_ms();
37 rpi_runtime::RuntimeSettings {
38 default_provider: settings.default_provider,
39 default_model: settings.default_model,
40 default_thinking_level: settings.default_thinking_level,
41 theme: settings.theme,
42 http_idle_timeout_ms: timeout,
43 }
44}
45fn resolve_with_factories(
46 provider: Option<&str>,
47 model: Option<&str>,
48 thinking: Option<ThinkingLevel>,
49 api_key: Option<&str>,
50 base_url: Option<&str>,
51 settings: settings::Settings,
52 factories: &RuntimeFactories,
53) -> Result<ResolvedModel, ResolveError> {
54 rpi_runtime::RuntimeConfig {
55 models: config::load_models_config()?,
56 credentials: config::read_auth()?,
57 settings: runtime_settings(settings),
58 }
59 .resolve(
60 &rpi_runtime::ModelRequest {
61 provider,
62 model,
63 thinking,
64 api_key,
65 base_url,
66 ..Default::default()
67 },
68 factories,
69 &CliValues,
70 )
71}
72pub fn resolve(
73 cli_provider: Option<&str>,
74 cli_model: Option<&str>,
75 cli_thinking: Option<ThinkingLevel>,
76 cli_api_key: Option<&str>,
77 cli_base_url: Option<&str>,
78) -> Result<ResolvedModel, ResolveError> {
79 let settings = settings::load_settings().unwrap_or_default();
80 resolve_with_settings(
81 cli_provider,
82 cli_model,
83 cli_thinking,
84 cli_api_key,
85 cli_base_url,
86 settings,
87 )
88}
89
90pub fn resolve_for_cwd(
92 cli_provider: Option<&str>,
93 cli_model: Option<&str>,
94 cli_thinking: Option<ThinkingLevel>,
95 cli_api_key: Option<&str>,
96 cli_base_url: Option<&str>,
97 cwd: &std::path::Path,
98 project_trusted: bool,
99) -> Result<ResolvedModel, ResolveError> {
100 let settings = settings::load_effective_model_settings(cwd, project_trusted)?;
101 resolve_with_settings(
102 cli_provider,
103 cli_model,
104 cli_thinking,
105 cli_api_key,
106 cli_base_url,
107 settings,
108 )
109}
110
111fn resolve_with_settings(
112 cli_provider: Option<&str>,
113 cli_model: Option<&str>,
114 cli_thinking: Option<ThinkingLevel>,
115 cli_api_key: Option<&str>,
116 cli_base_url: Option<&str>,
117 settings: settings::Settings,
118) -> Result<ResolvedModel, ResolveError> {
119 resolve_with_factories(
120 cli_provider,
121 cli_model,
122 cli_thinking,
123 cli_api_key,
124 cli_base_url,
125 settings,
126 &RuntimeFactories::default(),
127 )
128}
129
130pub fn resolve_with_extensions(
131 args: &crate::args::Args,
132 cwd: &std::path::Path,
133 trusted: bool,
134 session: &rpi_extensions::ExtensionSession,
135) -> Result<ResolvedModel, ResolveError> {
136 let settings = settings::load_effective_model_settings(cwd, trusted)?;
137 resolve_with_factories(
138 args.provider.as_deref(),
139 args.model.as_deref(),
140 args.thinking,
141 args.api_key.as_deref(),
142 args.base_url.as_deref(),
143 settings,
144 &factories_from_session(session),
145 )
146}
147
148fn factories_from_session(session: &rpi_extensions::ExtensionSession) -> RuntimeFactories {
149 let mut factories = RuntimeFactories::default();
150 if let Some(refresher) = crate::auth::PackageOAuthRefresher::new(session) {
151 factories.set_oauth_refresher(Arc::new(refresher));
152 }
153 if let Some(snapshot) = session.snapshot() {
154 for manifest in manifests_from_session(session) {
155 factories.declare(declaration(&manifest));
156 }
157 for provider in rpi_extensions::PluggableProvider::from_session(
158 session,
159 tokio::runtime::Handle::current(),
160 ) {
161 if let Some(record) = snapshot
162 .providers()
163 .iter()
164 .find(|record| record.provider_id == provider.id())
165 {
166 let api =
167 serde_json::from_value(serde_json::Value::String(record.api_style.clone()))
168 .expect("API strings include custom protocols");
169 factories.register(provider, api);
170 }
171 }
172 }
173 factories
174}
175pub fn catalog_all() -> Result<Vec<Model>, config::ConfigError> {
176 catalog_with_manifests(&[])
177}
178pub fn manifests_from_session(
179 session: &rpi_extensions::ExtensionSession,
180) -> Vec<rpi_extensions::ProviderManifest> {
181 session.snapshot().map_or_else(Vec::new, |snapshot| {
182 snapshot
183 .providers()
184 .iter()
185 .filter_map(|provider| snapshot.provider_manifest(&provider.provider_id).cloned())
186 .collect()
187 })
188}
189
190pub fn catalog_with_manifests(
191 manifests: &[rpi_extensions::ProviderManifest],
192) -> Result<Vec<Model>, config::ConfigError> {
193 catalog_with_diagnostics(manifests).map(|(models, _)| models)
194}
195pub(crate) fn catalog_with_diagnostics(
196 manifests: &[rpi_extensions::ProviderManifest],
197) -> Result<(Vec<Model>, Vec<String>), config::ConfigError> {
198 let declarations: Vec<_> = manifests.iter().map(declaration).collect();
199 Ok(rpi_runtime::provider::catalog_with_diagnostics(
200 config::load_models_config()?,
201 &declarations,
202 &CliValues,
203 ))
204}
205
206#[cfg(test)]
207mod tests;