use super::SessionOptions;
use crate::config::CodeConfig;
use crate::error::Result;
use crate::llm::LlmClient;
use anyhow::Context;
use std::sync::Arc;
pub(super) fn resolve_session_llm_client(
code_config: &CodeConfig,
opts: &SessionOptions,
session_id: Option<&str>,
) -> Result<Arc<dyn LlmClient>> {
if let Some(ref client) = opts.llm_client {
return Ok(Arc::clone(client));
}
let model_ref = if let Some(ref model) = opts.model {
model.as_str()
} else {
if opts.temperature.is_some() || opts.thinking_budget.is_some() {
tracing::warn!(
"temperature/thinking_budget set without model override - these will be ignored. \
Use with_model() to apply LLM parameter overrides."
);
}
code_config
.default_model
.as_deref()
.context("default_model must be set in 'provider/model' format")?
};
let (provider_name, model_id) = model_ref
.split_once('/')
.context("model format must be 'provider/model' (e.g., 'openai/gpt-4o')")?;
let mut llm_config = code_config
.llm_config(provider_name, model_id)
.with_context(|| {
format!("provider '{provider_name}' or model '{model_id}' not found in config")
})?;
if opts.model.is_some() {
if let Some(temp) = opts.temperature {
llm_config = llm_config.with_temperature(temp);
}
if let Some(budget) = opts.thinking_budget {
llm_config = llm_config.with_thinking_budget(budget);
}
}
if let Some(session_id) = session_id {
llm_config = llm_config.with_session_id(session_id);
}
Ok(crate::llm::create_client_with_config(llm_config))
}
pub(super) struct ResolvedSessionMemory {
pub(super) memory: Option<Arc<crate::memory::AgentMemory>>,
pub(super) init_warning: Option<String>,
}
pub(super) fn resolve_session_memory(opts: &SessionOptions) -> ResolvedSessionMemory {
let mut init_warning = None;
let store = if let Some(ref store) = opts.memory_store {
Some(Arc::clone(store))
} else if let Some(ref dir) = opts.file_memory_dir {
match tokio::runtime::Handle::try_current() {
Ok(handle) => {
let dir = dir.clone();
match tokio::task::block_in_place(|| {
handle.block_on(a3s_memory::FileMemoryStore::new(dir))
}) {
Ok(store) => Some(Arc::new(store) as Arc<dyn a3s_memory::MemoryStore>),
Err(e) => {
let msg = format!("Failed to create file memory store: {}", e);
tracing::warn!("{}", msg);
init_warning = Some(msg);
None
}
}
}
Err(_) => {
let msg = "No async runtime available for file memory store - memory disabled"
.to_string();
tracing::warn!("{}", msg);
init_warning = Some(msg);
None
}
}
} else {
None
};
ResolvedSessionMemory {
memory: store.map(|s| Arc::new(crate::memory::AgentMemory::new(s))),
init_warning,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::llm::{LlmResponse, Message, StreamEvent, ToolDefinition};
use anyhow::Result;
use async_trait::async_trait;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
struct DummyClient;
#[async_trait]
impl LlmClient for DummyClient {
async fn complete(
&self,
_: &[Message],
_: Option<&str>,
_: &[ToolDefinition],
) -> Result<LlmResponse> {
anyhow::bail!("resolver short-circuits before the client is called")
}
async fn complete_streaming(
&self,
_: &[Message],
_: Option<&str>,
_: &[ToolDefinition],
_: CancellationToken,
) -> Result<mpsc::Receiver<StreamEvent>> {
anyhow::bail!("not used")
}
}
#[test]
fn host_supplied_llm_client_overrides_factory() {
let config = CodeConfig::default();
let opts = SessionOptions::new().with_llm_client(Arc::new(DummyClient));
assert!(
resolve_session_llm_client(&config, &opts, None).is_ok(),
"with_llm_client must bypass provider/model config resolution"
);
}
#[test]
fn without_llm_client_missing_default_model_errors() {
let config = CodeConfig::default();
let opts = SessionOptions::new();
assert!(
resolve_session_llm_client(&config, &opts, None).is_err(),
"no host client + no default_model should error (control case)"
);
}
}
pub(super) fn resolve_session_store(
code_config: &CodeConfig,
opts: &SessionOptions,
) -> Option<Arc<dyn crate::store::SessionStore>> {
if opts.session_store.is_some() {
return opts.session_store.clone();
}
let dir = code_config.sessions_dir.as_ref()?;
match tokio::runtime::Handle::try_current() {
Ok(handle) => {
let dir = dir.clone();
match tokio::task::block_in_place(|| {
handle.block_on(crate::store::FileSessionStore::new(dir))
}) {
Ok(store) => Some(Arc::new(store) as Arc<dyn crate::store::SessionStore>),
Err(e) => {
tracing::warn!("Failed to create session store from sessions_dir: {}", e);
None
}
}
}
Err(_) => {
tracing::warn!("No async runtime for sessions_dir store - persistence disabled");
None
}
}
}