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