agent_sessions/history/
entry.rs1use 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 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 let mut field = |key: &str| match v.get(key) {
27 None | Some(Value::Null) => None,
28 Some(Value::String(s)) => Some(s.clone()),
29 _ => {
30 invalid_fields.push(key.into());
31 None
32 }
33 };
34 let session_id = field(id_key);
35 let text = field(text_key);
36 let project = field("project");
37 let at = match v.get(time_key) {
38 None | Some(Value::Null) => None,
39 Some(t) => {
40 let at = t.as_i64().and_then(|n| match agent {
41 Agent::ClaudeCode => DateTime::from_timestamp_millis(n),
42 Agent::Codex => DateTime::from_timestamp(n, 0),
43 });
44 if at.is_none() {
45 invalid_fields.push(time_key.into());
46 }
47 at
48 }
49 };
50 if strict && let Some(key) = invalid_fields.first() {
51 return Err(LineErrorKind::InvalidField(key.clone()));
52 }
53 Ok(HistoryEntry {
54 session_id,
55 text,
56 at,
57 timestamp: v.get(time_key).and_then(Value::as_i64),
58 project,
59 invalid_fields,
60 })
61}