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