Skip to main content

bamboo_engine/session_app/
resolution.rs

1//! Single source of truth for resolving the per-session execution "dance":
2//! provider-name derivation + provider_type + global auxiliary models +
3//! gold config, mapped into the snapshot structs the agent loop consumes.
4//!
5//! These resolution rules are load-bearing invariants — the resolved provider
6//! name (session model ref, falling back to `config.provider`) and the global
7//! (never session-bound) auxiliary model fallback. They were previously
8//! hand-rolled at every spawn/resume site; centralizing them here keeps the
9//! rules consistent by construction. The builder is pure orchestration over the
10//! existing atomic helpers, so behavior is identical to the hand-rolled copies.
11
12use std::sync::Arc;
13
14use bamboo_agent_core::Session;
15use bamboo_llm::{Config, ProviderRegistry};
16
17use crate::config::GoldConfig;
18use crate::model_areas::resolve_global_area_models;
19use crate::model_config_helper::{
20    resolve_gold_config, resolve_image_fallback, resolve_provider_type, GOLD_CONFIG_METADATA_KEY,
21};
22use crate::session_app::provider_model::session_effective_model_ref;
23use crate::session_app::types::ResumeConfigSnapshot;
24
25/// Resolve the [`ResumeConfigSnapshot`] for a session from a cloned `Config`
26/// snapshot + provider registry.
27///
28/// The auxiliary (fast / background / summarization) models are global —
29/// config-derived, keyed to the session's resolved provider only for fallback,
30/// never read from the session itself. `gold_config_override`, when `Some`,
31/// wins over the session-metadata-derived gold config.
32///
33/// Pure (no lock): callers clone their `Config` first, which works for both the
34/// async tokio lock and the cached `std::RwLock` snapshot.
35pub fn resolve_resume_config_snapshot(
36    config: &Config,
37    registry: &Arc<ProviderRegistry>,
38    session: &Session,
39    gold_config_override: Option<GoldConfig>,
40) -> ResumeConfigSnapshot {
41    let resolved_provider_name = session_effective_model_ref(session)
42        .map(|model_ref| model_ref.provider)
43        .unwrap_or_else(|| config.provider.clone());
44    let resolved_provider_type = resolve_provider_type(config, &resolved_provider_name, registry);
45    // Auxiliary models are global (config-derived), never session-bound.
46    let areas = resolve_global_area_models(config, &resolved_provider_name, registry);
47
48    ResumeConfigSnapshot {
49        provider_name: resolved_provider_name,
50        provider_type: resolved_provider_type,
51        fast_model: areas.fast.as_ref().map(|model| model.model_name.clone()),
52        fast_model_ref: areas.fast_ref.clone(),
53        background_model: areas
54            .background
55            .as_ref()
56            .map(|model| model.model_name.clone()),
57        background_model_ref: areas.background_ref.clone(),
58        background_model_provider: areas.background.map(|model| model.provider),
59        summarization_model: areas
60            .summarization
61            .as_ref()
62            .map(|model| model.model_name.clone()),
63        summarization_model_ref: areas.summarization_ref.clone(),
64        summarization_model_provider: areas.summarization.map(|model| model.provider),
65        disabled_tools: config.disabled_tool_names(),
66        disabled_skill_ids: config.disabled_skill_ids(),
67        image_fallback: resolve_image_fallback(config).ok().flatten(),
68        gold_config: gold_config_override.or_else(|| {
69            resolve_gold_config(
70                config,
71                session
72                    .metadata
73                    .get(GOLD_CONFIG_METADATA_KEY)
74                    .map(String::as_str),
75            )
76        }),
77    }
78}