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 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}