use std::path::Path;
use algocline_core::ExecutionMetrics;
use super::config::AppConfig;
use super::path::ContainedPath;
pub(super) fn write_transcript_log(
config: &AppConfig,
session_id: &str,
metrics: &ExecutionMetrics,
strategy: Option<&str>,
) {
let log_dir = match (&config.log_dir, config.log_enabled) {
(Some(dir), true) => dir,
_ => return,
};
let transcript = metrics.transcript_to_json();
if transcript.is_empty() {
return;
}
let stats = metrics.to_json();
let task_hint = transcript
.first()
.and_then(|e| e.get("prompt"))
.and_then(|p| p.as_str())
.map(|s| {
if s.len() <= 100 {
s.to_string()
} else {
let mut end = 100;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
format!("{}...", &s[..end])
}
});
let auto_stats = &stats["auto"];
let log_entry = serde_json::json!({
"session_id": session_id,
"strategy": strategy,
"task_hint": task_hint,
"stats": auto_stats,
"transcript": transcript,
});
if std::fs::create_dir_all(log_dir).is_err() {
return;
}
let path = match ContainedPath::child(log_dir, &format!("{session_id}.json")) {
Ok(p) => p,
Err(_) => return,
};
let content = match serde_json::to_string_pretty(&log_entry) {
Ok(s) => s,
Err(_) => return,
};
let _ = std::fs::write(&path, content);
let meta = serde_json::json!({
"session_id": session_id,
"strategy": strategy,
"task_hint": task_hint,
"elapsed_ms": auto_stats.get("elapsed_ms"),
"rounds": auto_stats.get("rounds"),
"llm_calls": auto_stats.get("llm_calls"),
"total_prompt_chars": auto_stats.get("total_prompt_chars"),
"total_response_chars": auto_stats.get("total_response_chars"),
"prompt_tokens": auto_stats.get("prompt_tokens"),
"response_tokens": auto_stats.get("response_tokens"),
"total_tokens": auto_stats.get("total_tokens"),
"notes_count": 0,
});
if let Ok(meta_path) = ContainedPath::child(log_dir, &format!("{session_id}.meta.json")) {
let _ = serde_json::to_string(&meta).map(|s| std::fs::write(&meta_path, s));
}
}
pub(super) fn append_note(
dir: &Path,
session_id: &str,
content: &str,
title: Option<&str>,
) -> Result<usize, String> {
let path = ContainedPath::child(dir, &format!("{session_id}.json"))?;
if !path.as_ref().exists() {
return Err(format!("Log file not found for session '{session_id}'"));
}
let raw = std::fs::read_to_string(&path).map_err(|e| format!("Failed to read log: {e}"))?;
let mut doc: serde_json::Value =
serde_json::from_str(&raw).map_err(|e| format!("Failed to parse log: {e}"))?;
let timestamp = {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
};
let note = serde_json::json!({
"timestamp": timestamp,
"title": title,
"content": content,
});
let notes = doc
.as_object_mut()
.ok_or("Log file is not a JSON object")?
.entry("notes")
.or_insert_with(|| serde_json::json!([]));
let arr = notes
.as_array_mut()
.ok_or("'notes' field is not an array")?;
arr.push(note);
let count = arr.len();
let output =
serde_json::to_string_pretty(&doc).map_err(|e| format!("Failed to serialize: {e}"))?;
std::fs::write(path.as_ref(), output).map_err(|e| format!("Failed to write log: {e}"))?;
if let Ok(meta_path) = ContainedPath::child(dir, &format!("{session_id}.meta.json")) {
if meta_path.as_ref().exists() {
if let Ok(raw) = std::fs::read_to_string(&meta_path) {
if let Ok(mut meta) = serde_json::from_str::<serde_json::Value>(&raw) {
meta["notes_count"] = serde_json::json!(count);
if let Ok(s) = serde_json::to_string(&meta) {
let _ = std::fs::write(&meta_path, s);
}
}
}
}
}
Ok(count)
}