// Oppsummerings-dispatcher — delegerer til synops-summarize CLI. // // Maskinrommet orkestrerer, CLI-verktøyet gjør jobben. // Ref: docs/retninger/unix_filosofi.md // // Jobbtype: "summarize_communication" // Payload: { "communication_id": "", "requested_by": "" } use std::process::Stdio; use uuid::Uuid; use crate::jobs::JobRow; use crate::stdb::StdbClient; /// Synops-summarize binary path. /// Søker i PATH, men kan overrides med SYNOPS_SUMMARIZE_BIN. fn summarize_bin() -> String { std::env::var("SYNOPS_SUMMARIZE_BIN") .unwrap_or_else(|_| "synops-summarize".to_string()) } /// Handler for summarize_communication-jobber. /// /// Spawner synops-summarize med --write for å gjøre alt arbeidet: /// LLM-kall, node-opprettelse, edge-skriving, ressurslogging. /// /// Payload forventer: /// - communication_id: UUID — kommunikasjonsnoden som skal oppsummeres /// - requested_by: UUID — brukeren som utløste oppsummeringen pub async fn handle_summarize_communication( job: &JobRow, _db: &sqlx::PgPool, _stdb: &StdbClient, ) -> Result { let communication_id: Uuid = job .payload .get("communication_id") .and_then(|v| v.as_str()) .and_then(|s| s.parse().ok()) .ok_or("Mangler gyldig communication_id i payload")?; let requested_by: Uuid = job .payload .get("requested_by") .and_then(|v| v.as_str()) .and_then(|s| s.parse().ok()) .ok_or("Mangler gyldig requested_by i payload")?; // Bygg kommando let bin = summarize_bin(); let mut cmd = tokio::process::Command::new(&bin); cmd.arg("--communication-id") .arg(communication_id.to_string()) .arg("--requested-by") .arg(requested_by.to_string()) .arg("--write"); // Sett miljøvariabler CLI-verktøyet trenger let db_url = std::env::var("DATABASE_URL") .map_err(|_| "DATABASE_URL ikke satt".to_string())?; cmd.env("DATABASE_URL", &db_url); // Videresend AI-relaterte env-variabler hvis satt if let Ok(v) = std::env::var("AI_GATEWAY_URL") { cmd.env("AI_GATEWAY_URL", v); } if let Ok(v) = std::env::var("LITELLM_MASTER_KEY") { cmd.env("LITELLM_MASTER_KEY", v); } if let Ok(v) = std::env::var("AI_SUMMARY_MODEL") { cmd.env("AI_SUMMARY_MODEL", v); } cmd.stdout(Stdio::piped()) .stderr(Stdio::piped()); tracing::info!( communication_id = %communication_id, requested_by = %requested_by, bin = %bin, "Starter synops-summarize" ); // Spawn og vent let child = cmd .spawn() .map_err(|e| format!("Kunne ikke starte {bin}: {e}"))?; let output = child .wait_with_output() .await .map_err(|e| format!("Feil ved kjøring av {bin}: {e}"))?; let stderr = String::from_utf8_lossy(&output.stderr); if !stderr.is_empty() { tracing::info!(stderr = %stderr, "synops-summarize stderr"); } if !output.status.success() { let code = output.status.code().unwrap_or(-1); return Err(format!( "synops-summarize feilet (exit {code}): {stderr}" )); } // Parse stdout som JSON — det er resultatet let stdout = String::from_utf8_lossy(&output.stdout); let result: serde_json::Value = serde_json::from_str(&stdout) .map_err(|e| format!("Kunne ikke parse synops-summarize output: {e}"))?; tracing::info!( communication_id = %communication_id, summary_node_id = result["summary_node_id"].as_str().unwrap_or("n/a"), status = result["status"].as_str().unwrap_or("unknown"), "synops-summarize fullført" ); Ok(result) }