bamboo_engine/
resolved_defaults.rs1use std::sync::Arc;
21
22use bamboo_domain::reasoning::ReasoningEffort;
23use bamboo_llm::{Config, ProviderRegistry};
24
25use crate::config::GoldConfig;
26use crate::ModelRoster;
27
28#[derive(Clone)]
31pub struct ResolvedDefaultRunConfig {
32 pub model_roster: ModelRoster,
36 pub reasoning_effort: Option<ReasoningEffort>,
37 pub gold_config: Option<GoldConfig>,
38 pub system_prompt: String,
40 pub base_system_prompt: String,
41 pub workspace_path: Option<String>,
42}
43
44pub fn resolve_default_run_config(
48 config_snapshot: &Config,
49 provider_registry: &Arc<ProviderRegistry>,
50) -> ResolvedDefaultRunConfig {
51 let model = config_snapshot.get_model().unwrap_or_default();
52 let provider_name = Some(config_snapshot.effective_default_provider().to_string());
53 let provider_type = provider_name.as_deref().and_then(|name| {
54 crate::model_config_helper::resolve_provider_type(config_snapshot, name, provider_registry)
55 });
56 let capability_provider_name = provider_name
57 .as_deref()
58 .unwrap_or(config_snapshot.effective_default_provider());
59 let areas = crate::model_areas::resolve_global_area_models(
62 config_snapshot,
63 capability_provider_name,
64 provider_registry,
65 );
66 let reasoning_effort = config_snapshot.get_reasoning_effort();
67 let base_system_prompt = crate::prompt_defaults::read_global_default_system_prompt_template();
68 let workspace_path = config_snapshot
69 .get_default_work_area_path()
70 .map(|path| bamboo_config::paths::path_to_display_string(&path));
71 let system_prompt = crate::context::assemble_system_prompt(
72 &base_system_prompt,
73 None,
74 workspace_path.as_deref(),
75 );
76 let model_roster = ModelRoster::from_areas(Some(model), provider_name, provider_type, areas);
77
78 ResolvedDefaultRunConfig {
79 model_roster,
80 reasoning_effort,
81 gold_config: crate::model_config_helper::resolve_gold_config(config_snapshot, None),
82 system_prompt,
83 base_system_prompt,
84 workspace_path,
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use bamboo_agent_core::tools::ToolSchema;
92 use bamboo_agent_core::Message;
93 use bamboo_config::{DefaultsConfig, FeatureFlags, OpenAIConfig, ProviderConfigs};
94 use bamboo_domain::ProviderModelRef;
95 use bamboo_llm::{LLMError, LLMProvider, LLMStream};
96 use std::collections::HashMap;
97
98 struct NoopProvider;
99
100 #[async_trait::async_trait]
101 impl LLMProvider for NoopProvider {
102 async fn chat_stream(
103 &self,
104 _messages: &[Message],
105 _tools: &[ToolSchema],
106 _max_output_tokens: Option<u32>,
107 _model: &str,
108 ) -> Result<LLMStream, LLMError> {
109 Err(LLMError::Api("noop".to_string()))
110 }
111 }
112
113 fn test_registry() -> Arc<ProviderRegistry> {
114 let mut providers: HashMap<String, Arc<dyn LLMProvider>> = HashMap::new();
115 providers.insert("openai".to_string(), Arc::new(NoopProvider));
116 Arc::new(ProviderRegistry::new(providers, "openai".to_string()))
117 }
118
119 fn config_with_area_defaults() -> Config {
120 let defaults = DefaultsConfig {
121 chat: ProviderModelRef::new("openai", "gpt-chat"),
122 fast: Some(ProviderModelRef::new("openai", "gpt-fast")),
123 task_summary: Some(ProviderModelRef::new("openai", "gpt-summary")),
124 vision: None,
125 memory_background: Some(ProviderModelRef::new("openai", "gpt-memory")),
126 planning: None,
127 search: None,
128 code_review: None,
129 sub_agent: None,
130 subagent_models: HashMap::new(),
131 };
132 Config {
133 provider: "openai".to_string(),
134 features: FeatureFlags {
135 provider_model_ref: true,
136 ..Default::default()
137 },
138 providers: ProviderConfigs {
139 openai: Some(OpenAIConfig {
140 api_key: "test-secret-key".to_string(),
141 model: Some("gpt-chat".to_string()),
142 ..Default::default()
143 }),
144 ..Default::default()
145 },
146 defaults: Some(defaults),
147 ..Config::default()
148 }
149 }
150
151 #[test]
154 fn resolve_default_run_config_resolves_distinct_area_models() {
155 let config = config_with_area_defaults();
156 let registry = test_registry();
157
158 let resolved = resolve_default_run_config(&config, ®istry);
159
160 assert_eq!(resolved.model_roster.model.as_deref(), Some("gpt-chat"));
161 assert_eq!(
162 resolved.model_roster.fast_model().as_deref(),
163 Some("gpt-fast")
164 );
165 assert_eq!(
166 resolved.model_roster.background_model().as_deref(),
167 Some("gpt-memory")
168 );
169 assert_eq!(
170 resolved.model_roster.summarization_model().as_deref(),
171 Some("gpt-summary")
172 );
173 }
174
175 #[test]
176 fn resolve_default_run_config_never_leaks_api_keys() {
177 let config = config_with_area_defaults();
178 let registry = test_registry();
179
180 let resolved = resolve_default_run_config(&config, ®istry);
181
182 assert!(!resolved.system_prompt.contains("test-secret-key"));
183 assert!(!resolved.base_system_prompt.contains("test-secret-key"));
184 let gold_json =
185 serde_json::to_string(&resolved.gold_config).unwrap_or_else(|_| "null".to_string());
186 assert!(!gold_json.contains("test-secret-key"));
187 }
188}