#![allow(
missing_docs,
reason = "fields mirror pi's TypeScript types; contracts are noted where they differ from the name"
)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::message::{Content, Message, Usage};
use crate::present;
pub const CURRENT_VERSION: u32 = 3;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum FileEntry {
Session(SessionHeader),
Message(MessageEntry),
ThinkingLevelChange(ThinkingLevelChangeEntry),
ModelChange(ModelChangeEntry),
Usage(UsageEntry),
Compaction(CompactionEntry),
BranchSummary(BranchSummaryEntry),
Custom(CustomEntry),
CustomMessage(CustomMessageEntry),
ContextEdit(ContextEditEntry),
Label(LabelEntry),
SessionInfo(SessionInfoEntry),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionHeader {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<u32>,
pub id: String,
pub timestamp: String,
pub cwd: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_session: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thinking_level: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub branched_from: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EntryMeta {
pub id: String,
pub parent_id: Option<String>,
pub timestamp: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub message: Message,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThinkingLevelChangeEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub thinking_level: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelChangeEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub provider: String,
pub model_id: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct UsageEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub kind: String,
pub provider: String,
pub model: String,
pub usage: Usage,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompactionEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub summary: String,
#[serde(
default,
deserialize_with = "present",
skip_serializing_if = "Option::is_none"
)]
pub first_kept_entry_id: Option<Option<String>>,
pub tokens_before: u64,
#[serde(
default,
deserialize_with = "present",
skip_serializing_if = "Option::is_none"
)]
pub details: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from_hook: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system_message: Option<Box<Message>>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BranchSummaryEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub from_id: String,
pub summary: String,
#[serde(
default,
deserialize_with = "present",
skip_serializing_if = "Option::is_none"
)]
pub details: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from_hook: Option<bool>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomEntry {
pub custom_type: String,
#[serde(
default,
deserialize_with = "present",
skip_serializing_if = "Option::is_none"
)]
pub data: Option<Value>,
#[serde(flatten)]
pub meta: EntryMeta,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomMessageEntry {
pub custom_type: String,
pub content: Content,
pub display: bool,
#[serde(
default,
deserialize_with = "present",
skip_serializing_if = "Option::is_none"
)]
pub details: Option<Value>,
#[serde(flatten)]
pub meta: EntryMeta,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContextEditEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub target_id: String,
pub replacement: Option<Replacement>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Replacement {
pub content: Content,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LabelEntry {
#[serde(flatten)]
pub meta: EntryMeta,
pub target_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SessionInfoEntry {
#[serde(flatten)]
pub meta: EntryMeta,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl FileEntry {
pub fn meta(&self) -> Option<&EntryMeta> {
Some(match self {
FileEntry::Session(_) => return None,
FileEntry::Message(entry) => &entry.meta,
FileEntry::ThinkingLevelChange(entry) => &entry.meta,
FileEntry::ModelChange(entry) => &entry.meta,
FileEntry::Usage(entry) => &entry.meta,
FileEntry::Compaction(entry) => &entry.meta,
FileEntry::BranchSummary(entry) => &entry.meta,
FileEntry::Custom(entry) => &entry.meta,
FileEntry::CustomMessage(entry) => &entry.meta,
FileEntry::ContextEdit(entry) => &entry.meta,
FileEntry::Label(entry) => &entry.meta,
FileEntry::SessionInfo(entry) => &entry.meta,
})
}
}