Skip to main content

rpi_cli/
provider.rs

1//! File, trust and native-extension adapters for the shared model runtime.
2use 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
89/// Resolve using upstream's global -> trusted-project settings precedence.
90pub 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;