use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct PreToolUseContext {
pub tool_name: String,
pub input: Value,
pub session_id: uuid::Uuid,
pub turn_number: usize,
}
#[derive(Debug, Clone)]
pub struct PostToolUseContext {
pub tool_name: String,
pub input: Value,
pub output: String,
pub is_error: bool,
pub duration_ms: u64,
pub session_id: uuid::Uuid,
pub turn_number: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompactTrigger {
Auto,
Manual,
}
impl From<crate::compact::types::CompactReason> for CompactTrigger {
fn from(reason: crate::compact::types::CompactReason) -> Self {
match reason {
crate::compact::types::CompactReason::ThresholdExceeded
| crate::compact::types::CompactReason::Emergency => CompactTrigger::Auto,
crate::compact::types::CompactReason::Manual => CompactTrigger::Manual,
}
}
}
#[derive(Debug, Clone)]
pub struct PreCompactContext {
pub trigger: CompactTrigger,
pub custom_instructions: Option<String>,
pub message_count: usize,
pub tokens_before: u64,
pub context_window: u64,
pub session_id: uuid::Uuid,
}
#[derive(Debug, Clone)]
pub struct PostCompactContext {
pub trigger: CompactTrigger,
pub messages_compacted: usize,
pub tokens_saved: u64,
pub tokens_after: u64,
pub duration_ms: u64,
pub session_id: uuid::Uuid,
}
#[derive(Debug, Clone, Default)]
pub struct CompactResult {
pub abort: bool,
pub abort_reason: Option<String>,
pub new_instructions: Option<String>,
pub additional_context: Vec<String>,
}
impl CompactResult {
#[must_use]
pub fn allow() -> Self {
Self::default()
}
pub fn abort(reason: impl Into<String>) -> Self {
Self {
abort: true,
abort_reason: Some(reason.into()),
..Self::default()
}
}
#[must_use]
pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
self.additional_context.push(ctx.into());
self
}
#[must_use]
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.new_instructions = Some(instructions.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SessionEndReason {
Complete,
Cancelled,
Error,
MaxTurns,
ContextOverflow,
}
#[derive(Debug, Clone)]
pub struct SessionStartContext {
pub session_id: uuid::Uuid,
pub model: String,
pub working_directory: String,
}
#[derive(Debug, Clone)]
pub struct SessionEndContext {
pub session_id: uuid::Uuid,
pub reason: SessionEndReason,
pub total_turns: usize,
pub total_tokens: u64,
pub duration_secs: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_result_allow_is_default() {
let result = CompactResult::allow();
assert!(!result.abort);
assert!(result.abort_reason.is_none());
assert!(result.new_instructions.is_none());
assert!(result.additional_context.is_empty());
}
#[test]
fn compact_result_abort_sets_reason() {
let result = CompactResult::abort("too risky");
assert!(result.abort);
assert_eq!(result.abort_reason.as_deref(), Some("too risky"));
}
#[test]
fn compact_result_builder_pattern() {
let result = CompactResult::allow()
.with_context("keep file X")
.with_context("remember Y")
.with_instructions("focus on Z");
assert!(!result.abort);
assert_eq!(result.additional_context.len(), 2);
assert_eq!(result.new_instructions.as_deref(), Some("focus on Z"));
}
#[test]
fn compact_trigger_serialization() {
for trigger in [CompactTrigger::Auto, CompactTrigger::Manual] {
let json = serde_json::to_string(&trigger).expect("serialize");
let back: CompactTrigger = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, trigger);
}
}
#[test]
fn session_end_reason_serialization() {
let reasons = [
SessionEndReason::Complete,
SessionEndReason::Cancelled,
SessionEndReason::Error,
SessionEndReason::MaxTurns,
SessionEndReason::ContextOverflow,
];
for reason in reasons {
let json = serde_json::to_string(&reason).expect("serialize");
let back: SessionEndReason = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, reason);
}
}
#[test]
fn compact_result_abort_with_no_additional_fields() {
let result = CompactResult::abort("reason");
assert!(result.abort);
assert_eq!(result.abort_reason.as_deref(), Some("reason"));
assert!(
result.new_instructions.is_none(),
"should have no new_instructions"
);
assert!(
result.additional_context.is_empty(),
"should have empty additional_context"
);
}
}