use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::domain::pi_rpc::{PiSessionStats, ThinkingLevel};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ChatStatus {
Starting,
Idle,
Streaming,
Compacting,
Retrying,
Settled,
Exited,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum ChatRole {
User,
Assistant,
Tool,
System,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(
tag = "type",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
pub(crate) enum ChatContent {
Text {
text: String,
},
Thinking {
text: String,
},
ToolCall {
id: String,
name: String,
arguments: Value,
},
ToolResult {
tool_call_id: String,
content: Value,
is_error: bool,
},
Unknown {
value: Value,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatMessage {
pub(crate) id: String,
pub(crate) role: ChatRole,
pub(crate) content: Vec<ChatContent>,
pub(crate) timestamp: Option<i64>,
pub(crate) provider: Option<String>,
pub(crate) model: Option<String>,
pub(crate) usage: Option<Value>,
pub(crate) stop_reason: Option<ChatStopReason>,
pub(crate) error_message: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum ChatStopReason {
Stop,
Length,
ToolUse,
Error,
Aborted,
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct ChatModel {
pub(crate) provider: String,
pub(crate) id: String,
pub(crate) value: Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatSessionState {
pub(crate) session_id: String,
pub(crate) pi_session_id: String,
pub(crate) session_file: Option<String>,
pub(crate) workspace: PathBuf,
pub(crate) model: Option<ChatModel>,
pub(crate) thinking_level: ThinkingLevel,
pub(crate) status: ChatStatus,
pub(crate) is_streaming: bool,
pub(crate) is_compacting: bool,
pub(crate) name: Option<String>,
pub(crate) message_count: u64,
pub(crate) pending_message_count: u64,
pub(crate) stats: Option<Box<PiSessionStats>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatExtensionUiRecord {
pub(crate) id: String,
pub(crate) method: String,
pub(crate) payload: Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatExtensionKeyedRecord {
pub(crate) key: String,
pub(crate) id: String,
pub(crate) method: String,
pub(crate) payload: Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatExtensionState {
pub(crate) pending_requests: Vec<ChatExtensionUiRecord>,
pub(crate) notifications: Vec<ChatExtensionUiRecord>,
pub(crate) statuses: Vec<ChatExtensionKeyedRecord>,
pub(crate) widgets: Vec<ChatExtensionKeyedRecord>,
pub(crate) editor_text: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatTranscriptPage {
pub(crate) messages: Vec<ChatMessage>,
pub(crate) tools: Vec<ChatToolExecution>,
pub(crate) older_cursor: Option<String>,
pub(crate) newer_cursor: Option<String>,
pub(crate) page_bytes: usize,
pub(crate) event_cursor: u64,
pub(crate) generation: String,
}
#[allow(dead_code)]
pub(crate) const MAX_TRANSCRIPT_PAGE_BYTES: usize = 4 * 1024 * 1024;
pub(crate) const TRANSCRIPT_DELIVERY_HEADROOM_BYTES: usize = 1024;
pub(crate) const MAX_TRANSCRIPT_DELIVERY_BYTES: usize =
MAX_TRANSCRIPT_PAGE_BYTES - TRANSCRIPT_DELIVERY_HEADROOM_BYTES;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum TranscriptDirection {
Older,
Newer,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatSnapshot {
pub(crate) state: ChatSessionState,
pub(crate) transcript: ChatTranscriptPage,
pub(crate) extension: ChatExtensionState,
pub(crate) event_sequence: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PromptAccepted {
pub(crate) request_id: String,
pub(crate) user_message_id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) struct ChatSessionStarted {
pub(crate) state: ChatSessionState,
pub(crate) pid: Option<u32>,
pub(crate) prompt: PromptAccepted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ChatDeltaKind {
Text,
Thinking,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ChatToolStatus {
Running,
Succeeded,
Failed,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChatToolExecution {
pub(crate) id: String,
pub(crate) name: String,
pub(crate) arguments: Value,
pub(crate) output: Option<Value>,
pub(crate) is_error: bool,
pub(crate) status: ChatToolStatus,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(
tag = "type",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
pub(crate) enum ChatEvent {
SessionStarted {
session_id: String,
started: ChatSessionStarted,
},
StatusChanged {
session_id: String,
status: ChatStatus,
},
PromptAccepted {
session_id: String,
request_id: String,
user_message_id: String,
},
AgentEnded {
session_id: String,
will_retry: bool,
},
MessageStarted {
session_id: String,
message: ChatMessage,
},
ContentDelta {
session_id: String,
message_id: String,
content_index: usize,
kind: ChatDeltaKind,
delta: String,
},
ToolCallDelta {
session_id: String,
message_id: String,
content_index: usize,
delta: String,
},
ToolCallCompleted {
session_id: String,
message_id: String,
content_index: usize,
tool_call: ChatContent,
},
MessageEnded {
session_id: String,
message: ChatMessage,
},
ToolExecutionChanged {
session_id: String,
execution: ChatToolExecution,
output_is_accumulated: bool,
},
QueueUpdated {
session_id: String,
steering: Vec<String>,
follow_up: Vec<String>,
},
CompactionChanged {
session_id: String,
started: bool,
reason: String,
result: Option<Value>,
aborted: bool,
will_retry: bool,
error: Option<String>,
},
RetryChanged {
session_id: String,
started: bool,
attempt: u64,
max_attempts: Option<u64>,
delay_ms: Option<u64>,
success: Option<bool>,
error: Option<String>,
},
ThinkingLevelChanged {
session_id: String,
level: ThinkingLevel,
},
SessionInfoChanged {
session_id: String,
name: Option<String>,
},
UsageUpdated {
session_id: String,
message_id: String,
usage: Value,
},
StatsUpdated {
session_id: String,
stats: PiSessionStats,
},
ExtensionError {
session_id: String,
extension_path: String,
event: String,
error: String,
},
ExtensionUiRequest {
session_id: String,
id: String,
method: String,
payload: Value,
},
Error {
session_id: String,
code: String,
message: String,
recoverable: bool,
},
Exited {
session_id: String,
message: String,
},
ResyncRequired {
session_id: String,
reason: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SequencedChatEvent {
pub(crate) event_sequence: Option<u64>,
pub(crate) event: ChatEvent,
}
impl ChatEvent {
pub(crate) fn session_id(&self) -> &str {
match self {
Self::SessionStarted { session_id, .. }
| Self::StatusChanged { session_id, .. }
| Self::PromptAccepted { session_id, .. }
| Self::AgentEnded { session_id, .. }
| Self::MessageStarted { session_id, .. }
| Self::ContentDelta { session_id, .. }
| Self::ToolCallDelta { session_id, .. }
| Self::ToolCallCompleted { session_id, .. }
| Self::MessageEnded { session_id, .. }
| Self::ToolExecutionChanged { session_id, .. }
| Self::QueueUpdated { session_id, .. }
| Self::CompactionChanged { session_id, .. }
| Self::RetryChanged { session_id, .. }
| Self::ThinkingLevelChanged { session_id, .. }
| Self::SessionInfoChanged { session_id, .. }
| Self::UsageUpdated { session_id, .. }
| Self::StatsUpdated { session_id, .. }
| Self::ExtensionError { session_id, .. }
| Self::ExtensionUiRequest { session_id, .. }
| Self::Error { session_id, .. }
| Self::Exited { session_id, .. }
| Self::ResyncRequired { session_id, .. } => session_id,
}
}
}