use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
#[derive(Debug, Clone)]
pub struct ConversationMessage {
pub entry_id: String,
pub kind: MessageKind,
}
impl ConversationMessage {
#[must_use]
pub fn new(entry_id: impl Into<String>, kind: MessageKind) -> Self {
Self {
entry_id: entry_id.into(),
kind,
}
}
#[must_use]
pub fn role(&self) -> &str {
match &self.kind {
MessageKind::TextContent(tc) => tc.role.as_str(),
MessageKind::AssistantResponse(_) => "assistant",
MessageKind::ToolResultData(_) | MessageKind::BashOutput(_) => "user",
}
}
#[must_use]
pub fn role_label(&self) -> String {
match &self.kind {
MessageKind::TextContent(tc) => {
if tc.role.is_empty() {
"unknown".to_string()
} else {
tc.role.clone()
}
}
MessageKind::AssistantResponse(_) => "assistant".to_string(),
MessageKind::ToolResultData(tr) => format!("tool/{}", tr.tool_name),
MessageKind::BashOutput(_) => "bash".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub enum MessageKind {
TextContent(TextContent),
AssistantResponse(AssistantResponse),
ToolResultData(ToolResultData),
BashOutput(BashOutput),
}
#[derive(Debug, Clone)]
pub struct TextContent {
pub role: String,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct AssistantResponse {
pub thinking: Vec<String>,
pub tool_calls: Vec<ToolCall>,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct ToolResultData {
pub call_id: String,
pub tool_name: String,
pub content: String,
pub is_error: bool,
}
#[derive(Debug, Clone)]
pub struct BashOutput {
pub command: String,
pub output: String,
}
#[derive(Debug, Clone)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: JsonValue,
}
#[derive(Debug, Clone)]
pub struct Entry {
pub id: String,
pub parent_id: String,
}
#[derive(Debug, Clone)]
pub struct Context {
pub entries: Vec<Entry>,
pub messages: Vec<ConversationMessage>,
pub cwd: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SearchHit {
pub entry_id: String,
pub score: f64,
pub role: String,
pub text: String,
pub files: Vec<String>,
pub terms: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ContextListing {
pub id: String,
pub provider_id: ProviderId,
pub path: PathBuf,
pub native_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProviderId {
Claude {
timestamp: String,
cwd: String,
},
Codex {
timestamp: String,
cwd: String,
},
Opencode {
count: u32,
first_time: String,
last_time: String,
},
Crush {
count: u32,
},
Goose {
name: String,
count: u32,
cwd: String,
},
Gemini {
timestamp: String,
count: u32,
},
Pi {
cwd: String,
},
}
impl ProviderId {
#[must_use]
pub fn sort_key(&self) -> &str {
match self {
Self::Claude { timestamp, .. }
| Self::Codex { timestamp, .. }
| Self::Gemini { timestamp, .. } => timestamp,
Self::Opencode { last_time, .. } => last_time,
Self::Crush { .. } | Self::Goose { .. } | Self::Pi { .. } => "",
}
}
#[must_use]
pub fn timestamp(&self) -> &str {
match self {
Self::Claude { timestamp, .. }
| Self::Codex { timestamp, .. }
| Self::Gemini { timestamp, .. } => timestamp,
_ => "-",
}
}
#[must_use]
pub fn cwd(&self) -> &str {
match self {
Self::Claude { cwd, .. }
| Self::Codex { cwd, .. }
| Self::Goose { cwd, .. }
| Self::Pi { cwd } => cwd,
_ => "-",
}
}
#[must_use]
pub fn count(&self) -> String {
match self {
Self::Opencode { count, .. }
| Self::Crush { count }
| Self::Goose { count, .. }
| Self::Gemini { count, .. } => count.to_string(),
_ => "-".to_string(),
}
}
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::Goose { name, .. } if !name.is_empty() => name,
_ => "-",
}
}
#[must_use]
pub fn time_range(&self) -> String {
match self {
Self::Opencode {
first_time,
last_time,
..
} => format!("{first_time}..{last_time}"),
_ => "-".to_string(),
}
}
}