Skip to main content

systemprompt_cli/session/resolution/
mod.rs

1mod helpers;
2
3use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6use systemprompt_cloud::{SessionKey, SessionStore};
7use systemprompt_config::{ProfileBootstrap, SecretsBootstrap};
8use systemprompt_loader::ProfileLoader;
9use systemprompt_logging::CliService;
10use systemprompt_models::Profile;
11
12use super::context::CliSessionContext;
13use crate::CliConfig;
14use crate::cli_settings::{OutputFormat, VerbosityLevel};
15use crate::paths::ResolvedPaths;
16use helpers::{
17    create_new_session, extract_profile_name, initialize_profile_bootstraps,
18    resolve_profile_path_from_session, resolve_profile_path_without_session, try_session_from_env,
19    try_validate_context,
20};
21
22pub(super) struct ProfileContext<'a> {
23    pub name: &'a str,
24    pub path: PathBuf,
25}
26
27async fn get_session_for_profile(
28    profile_input: &str,
29    config: &CliConfig,
30) -> Result<CliSessionContext> {
31    let (profile_path, profile) = crate::shared::resolve_profile_with_data(profile_input)
32        .map_err(|e| anyhow::anyhow!("{}", e))?;
33
34    if !ProfileBootstrap::is_initialized() {
35        ProfileBootstrap::init_from_path(&profile_path)
36            .with_context(|| format!("Failed to initialize profile '{}'", profile_input))?;
37    }
38
39    if !SecretsBootstrap::is_initialized() {
40        SecretsBootstrap::try_init().with_context(|| {
41            "Failed to initialize secrets. Check your profile's secrets configuration."
42        })?;
43    }
44
45    get_session_for_loaded_profile(&profile, &profile_path, config).await
46}
47
48async fn get_session_for_loaded_profile(
49    profile: &Profile,
50    profile_path: &Path,
51    config: &CliConfig,
52) -> Result<CliSessionContext> {
53    if let Some(ctx) = try_session_from_env(profile) {
54        return Ok(ctx);
55    }
56
57    let profile_name = extract_profile_name(profile_path)?;
58    let tenant_id = profile.cloud.as_ref().and_then(|c| c.tenant_id.as_ref());
59    let session_key = SessionKey::from_tenant_id(tenant_id);
60    let sessions_dir = ResolvedPaths::discover().sessions_dir();
61    let mut store = SessionStore::load_or_create(&sessions_dir)?;
62
63    if let Some(mut session) = store.get_valid_session(&session_key).cloned() {
64        session.touch();
65
66        if let Some(refreshed) = try_validate_context(&mut session, &profile_name).await {
67            session = refreshed;
68        }
69
70        store.upsert_session(&session_key, session.clone());
71        store.save(&sessions_dir)?;
72        return Ok(CliSessionContext {
73            session,
74            profile: profile.clone(),
75        });
76    }
77
78    let session_email_hint = store
79        .get_session(&session_key)
80        .map(|s| s.user_email.to_string());
81
82    let profile_ctx = ProfileContext {
83        name: &profile_name,
84        path: profile_path.to_path_buf(),
85    };
86
87    let session = create_new_session(
88        profile,
89        &profile_ctx,
90        &session_key,
91        config,
92        session_email_hint.as_deref(),
93    )
94    .await?;
95
96    store.upsert_session(&session_key, session.clone());
97    store.set_active_with_profile(&session_key, &profile_name);
98    store.save(&sessions_dir)?;
99
100    if session.session_token.as_str().is_empty() {
101        anyhow::bail!("Session token is empty. Session creation failed.");
102    }
103
104    Ok(CliSessionContext {
105        session,
106        profile: profile.clone(),
107    })
108}
109
110async fn try_session_from_active_key(config: &CliConfig) -> Result<Option<CliSessionContext>> {
111    let paths = ResolvedPaths::discover();
112    let sessions_dir = paths.sessions_dir();
113    let store = SessionStore::load_or_create(&sessions_dir)?;
114
115    let Some(ref active_key_str) = store.active_key else {
116        return Ok(None);
117    };
118
119    let active_key = store
120        .active_session_key()
121        .ok_or_else(|| anyhow::anyhow!("Invalid active session key: {}", active_key_str))?;
122
123    let active_profile = store.active_profile_name.as_deref();
124
125    let profile_path = if let Some(session) = store.active_session() {
126        match resolve_profile_path_from_session(session, active_profile)? {
127            Some(path) => path,
128            None => return Ok(None),
129        }
130    } else {
131        resolve_profile_path_without_session(&paths, &store, &active_key, active_profile)?
132    };
133
134    let profile = ProfileLoader::load_from_path(&profile_path).with_context(|| {
135        format!(
136            "Failed to load profile from stored path: {}",
137            profile_path.display()
138        )
139    })?;
140
141    initialize_profile_bootstraps(&profile_path)?;
142
143    let ctx = get_session_for_loaded_profile(&profile, &profile_path, config).await?;
144    Ok(Some(ctx))
145}
146
147pub async fn get_or_create_session(config: &CliConfig) -> Result<CliSessionContext> {
148    let ctx = resolve_session(config).await?;
149
150    let banner_requested = config.verbosity >= VerbosityLevel::Verbose;
151    let banner_warranted = ctx.profile.target.is_cloud();
152    if config.is_interactive()
153        && config.output_format == OutputFormat::Table
154        && config.verbosity != VerbosityLevel::Quiet
155        && (banner_requested || banner_warranted)
156    {
157        let tenant = ctx
158            .session
159            .tenant_key
160            .as_ref()
161            .map_or("local", systemprompt_identifiers::TenantId::as_str);
162        CliService::session_context_with_url(
163            ctx.session.profile_name.as_str(),
164            &ctx.session.session_id,
165            Some(tenant),
166            Some(&ctx.profile.server.api_external_url),
167        );
168    }
169
170    Ok(ctx)
171}
172
173async fn resolve_session(config: &CliConfig) -> Result<CliSessionContext> {
174    if let Some(ref profile_name) = config.profile_override {
175        return get_session_for_profile(profile_name, config).await;
176    }
177
178    let env_profile_set = std::env::var("SYSTEMPROMPT_PROFILE").is_ok();
179
180    if !env_profile_set {
181        if let Some(ctx) = try_session_from_active_key(config).await? {
182            return Ok(ctx);
183        }
184    }
185
186    let profile = ProfileBootstrap::get()
187        .map_err(|_e| {
188            anyhow::anyhow!(
189                "Profile required.\n\nSet SYSTEMPROMPT_PROFILE environment variable to your \
190                 profile.yaml path, or use --profile <name>."
191            )
192        })?
193        .clone();
194
195    let profile_path_str = ProfileBootstrap::get_path().map_err(|_e| {
196        anyhow::anyhow!(
197            "Profile path required.\n\nSet SYSTEMPROMPT_PROFILE environment variable or use \
198             --profile <name>."
199        )
200    })?;
201
202    let profile_path = Path::new(profile_path_str);
203    get_session_for_loaded_profile(&profile, profile_path, config).await
204}