Skip to main content

agent_sessions/history/
entry.rs

1use crate::{Agent, LineErrorKind};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct HistoryEntry {
8    pub session_id: Option<String>,
9    pub text: Option<String>,
10    pub at: Option<DateTime<Utc>>,
11    /// Native integer timestamp: milliseconds for Claude, seconds for Codex.
12    /// Retained even when outside the representable datetime range.
13    pub timestamp: Option<i64>,
14    pub project: Option<String>,
15    pub invalid_fields: Vec<String>,
16}
17pub fn history_entry(agent: Agent, v: &Value, strict: bool) -> Result<HistoryEntry, LineErrorKind> {
18    let mut invalid_fields: Vec<String> = Vec::new();
19    if !v.is_object() {
20        invalid_fields.push("record".into());
21    }
22    let (id_key, text_key, time_key) = match agent {
23        Agent::ClaudeCode => ("sessionId", "display", "timestamp"),
24        Agent::Codex => ("session_id", "text", "ts"),
25        _ => {
26            return Err(LineErrorKind::InvalidField(
27                "unsupported history format".into(),
28            ));
29        }
30    };
31    let mut field = |key: &str| match v.get(key) {
32        None | Some(Value::Null) => None,
33        Some(Value::String(s)) => Some(s.clone()),
34        _ => {
35            invalid_fields.push(key.into());
36            None
37        }
38    };
39    let session_id = field(id_key);
40    let text = field(text_key);
41    let project = field("project");
42    let at = match v.get(time_key) {
43        None | Some(Value::Null) => None,
44        Some(t) => {
45            let at = t.as_i64().and_then(|n| match agent {
46                Agent::ClaudeCode => DateTime::from_timestamp_millis(n),
47                Agent::Codex => DateTime::from_timestamp(n, 0),
48                _ => None,
49            });
50            if at.is_none() {
51                invalid_fields.push(time_key.into());
52            }
53            at
54        }
55    };
56    if strict && let Some(key) = invalid_fields.first() {
57        return Err(LineErrorKind::InvalidField(key.clone()));
58    }
59    Ok(HistoryEntry {
60        session_id,
61        text,
62        at,
63        timestamp: v.get(time_key).and_then(Value::as_i64),
64        project,
65        invalid_fields,
66    })
67}