use actix_web::{web, HttpResponse, Responder};
use super::{ChatRequest, ChatResponse};
use crate::app_state::AppState;
mod images;
mod request;
fn sync_runtime_workspace(session_id: &str, workspace_path: Option<&str>) {
let preferred = workspace_path
.map(str::trim)
.filter(|s| !s.is_empty())
.map(std::path::PathBuf::from)
.and_then(|path| std::fs::canonicalize(&path).ok().or(Some(path)))
.filter(|path| path.is_dir());
let _ = bamboo_tools::tools::workspace_state::ensure_session_workspace(session_id, preferred);
}
#[cfg(test)]
mod tests;
pub async fn handler(state: web::Data<AppState>, req: web::Json<ChatRequest>) -> impl Responder {
let session_id = request::resolve_session_id(req.session_id.as_deref());
let model = match request::validate_and_normalize_model(req.model.as_str()) {
Ok(model) => model,
Err(response) => return response,
};
let global_default_prompt =
crate::prompt_defaults::read_global_default_system_prompt_template();
let builtin_fallback_prompt = crate::app_state::DEFAULT_BASE_PROMPT;
let workspace_path = request::optional_non_empty(req.workspace_path.as_deref());
let data_dir = Some(state.app_data_dir.clone());
sync_runtime_workspace(
&session_id,
workspace_path.map(str::trim).filter(|s| !s.is_empty()),
);
let input = crate::session_app::types::ChatTurnInput {
session_id: session_id.clone(),
model: model.clone(),
model_ref: req.model_ref.clone(),
provider: req.provider.clone(),
message: req.message.clone(),
system_prompt: request::optional_non_empty(req.system_prompt.as_deref()).map(String::from),
enhance_prompt: request::optional_non_empty(req.enhance_prompt.as_deref())
.map(String::from),
workspace_path: workspace_path.map(String::from),
selected_skill_ids: req.selected_skill_ids.clone(),
copilot_conclusion_with_options_enhancement_enabled: req
.copilot_conclusion_with_options_enhancement_enabled,
data_dir,
};
let mut session = match crate::session_app::chat::prepare_chat_turn(
state.as_ref(),
input,
global_default_prompt.as_str(),
builtin_fallback_prompt,
)
.await
{
Ok(session) => session,
Err(error) => {
tracing::error!("Chat turn preparation failed: {error}");
return HttpResponse::InternalServerError().json(serde_json::json!({
"error": format!("Failed to prepare chat: {error}")
}));
}
};
if let Err(response) =
images::append_user_message(&state, &mut session, &req.message, req.images.as_deref()).await
{
return response;
}
state.save_and_cache_session(&session).await;
HttpResponse::Created().json(ChatResponse {
session_id: session_id.clone(),
stream_url: format!("/api/v1/events/{}", session_id),
status: "streaming".to_string(),
})
}