move journal types from agent/journal.rs to thought/context.rs
JournalEntry, parse_journal, parse_journal_text, parse_header_timestamp, and default_journal_path consolidated into thought/context.rs. Delete the duplicate agent/journal.rs (235 lines). Update all references. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
e0a54a3b43
commit
01bfbc0dad
6 changed files with 82 additions and 247 deletions
|
|
@ -5,10 +5,82 @@
|
|||
// take inputs and return new values. State mutation happens in agent.rs.
|
||||
|
||||
// TODO: move Message, ContextState, etc. to thought layer
|
||||
use crate::agent::journal;
|
||||
use crate::agent::types::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use tiktoken_rs::CoreBPE;
|
||||
use std::path::Path;
|
||||
|
||||
/// A single journal entry with its timestamp and content.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JournalEntry {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// Parse journal entries from the journal file. Returns entries sorted
|
||||
/// by timestamp (oldest first). Entries with unparseable timestamps
|
||||
/// are skipped.
|
||||
pub fn parse_journal(path: &Path) -> Vec<JournalEntry> {
|
||||
let text = match std::fs::read_to_string(path) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
parse_journal_text(&text)
|
||||
}
|
||||
|
||||
/// Parse journal entries from text.
|
||||
pub fn parse_journal_text(text: &str) -> Vec<JournalEntry> {
|
||||
let mut entries = Vec::new();
|
||||
let mut current_timestamp: Option<DateTime<Utc>> = None;
|
||||
let mut current_content = String::new();
|
||||
|
||||
for line in text.lines() {
|
||||
if let Some(ts) = parse_header_timestamp(line) {
|
||||
if let Some(prev_ts) = current_timestamp.take() {
|
||||
let content = current_content.trim().to_string();
|
||||
if !content.is_empty() {
|
||||
entries.push(JournalEntry { timestamp: prev_ts, content });
|
||||
}
|
||||
}
|
||||
current_timestamp = Some(ts);
|
||||
current_content.clear();
|
||||
} else if current_timestamp.is_some() {
|
||||
current_content.push_str(line);
|
||||
current_content.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ts) = current_timestamp {
|
||||
let content = current_content.trim().to_string();
|
||||
if !content.is_empty() {
|
||||
entries.push(JournalEntry { timestamp: ts, content });
|
||||
}
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
/// Try to parse a line as a journal header (## TIMESTAMP [— title]).
|
||||
fn parse_header_timestamp(line: &str) -> Option<DateTime<Utc>> {
|
||||
let line = line.trim();
|
||||
if !line.starts_with("## ") { return None; }
|
||||
let rest = line[3..].trim();
|
||||
if !rest.starts_with(|c: char| c.is_ascii_digit()) { return None; }
|
||||
let ts_str = rest.split_once(' ').map_or(rest, |(ts, _)| ts);
|
||||
for fmt in ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M"] {
|
||||
if let Ok(naive) = NaiveDateTime::parse_from_str(ts_str, fmt) {
|
||||
return Some(naive.and_utc());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Default journal file path.
|
||||
pub fn default_journal_path() -> std::path::PathBuf {
|
||||
dirs::home_dir()
|
||||
.unwrap_or_default()
|
||||
.join(".consciousness/journal.md")
|
||||
}
|
||||
|
||||
/// Look up a model's context window size in tokens.
|
||||
pub fn model_context_window(_model: &str) -> usize {
|
||||
|
|
@ -47,8 +119,8 @@ pub fn build_context_window(
|
|||
model: &str,
|
||||
tokenizer: &CoreBPE,
|
||||
) -> (Vec<Message>, String) {
|
||||
let journal_path = journal::default_journal_path();
|
||||
let all_entries = journal::parse_journal(&journal_path);
|
||||
let journal_path = default_journal_path();
|
||||
let all_entries = parse_journal(&journal_path);
|
||||
dbglog!("[ctx] {} journal entries from {}", all_entries.len(), journal_path.display());
|
||||
let count = |s: &str| tokenizer.encode_with_special_tokens(s).len();
|
||||
|
||||
|
|
@ -96,7 +168,7 @@ pub fn plan_context(
|
|||
system_prompt: &str,
|
||||
context_message: &str,
|
||||
recent: &[Message],
|
||||
entries: &[journal::JournalEntry],
|
||||
entries: &[JournalEntry],
|
||||
model: &str,
|
||||
count: &dyn Fn(&str) -> usize,
|
||||
) -> ContextPlan {
|
||||
|
|
@ -184,7 +256,7 @@ pub fn plan_context(
|
|||
}
|
||||
|
||||
pub fn render_journal_text(
|
||||
entries: &[journal::JournalEntry],
|
||||
entries: &[JournalEntry],
|
||||
plan: &ContextPlan,
|
||||
) -> String {
|
||||
let has_journal = plan.header_start < plan.entry_count;
|
||||
|
|
@ -285,7 +357,7 @@ fn truncate_at_section(text: &str, max_tokens: usize, count: &dyn Fn(&str) -> us
|
|||
|
||||
fn find_journal_cutoff(
|
||||
conversation: &[Message],
|
||||
newest_entry: Option<&journal::JournalEntry>,
|
||||
newest_entry: Option<&JournalEntry>,
|
||||
) -> usize {
|
||||
let cutoff = match newest_entry {
|
||||
Some(entry) => entry.timestamp,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ pub fn write_entry(args: &serde_json::Value) -> Result<String> {
|
|||
.as_str()
|
||||
.context("entry is required")?;
|
||||
|
||||
let journal_path = crate::agent::journal::default_journal_path();
|
||||
let journal_path = crate::thought::context::default_journal_path();
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = journal_path.parent() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue