use crate::{agent::cancellation::AgentCancellation, providers::ToolCall, tools::ToolResult};
use std::path::PathBuf;
use super::redact_sensitive_text;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ContextUsageSource {
FallbackEstimate,
TokenizerEstimate,
FallbackProjection,
TokenizerProjection,
LastProviderUsage,
ProviderExact,
ProviderPartial,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ProviderContextInjectionDisplay {
pub(crate) phase: crate::hooks::HookPhase,
pub(crate) label: String,
pub(crate) target: String,
pub(crate) status: String,
pub(crate) item_count: usize,
pub(crate) byte_count: usize,
pub(crate) content: Option<String>,
}
impl ProviderContextInjectionDisplay {
pub(crate) fn new(
phase: crate::hooks::HookPhase,
label: impl Into<String>,
target: impl Into<String>,
status: impl Into<String>,
item_count: usize,
byte_count: usize,
content: Option<String>,
) -> Self {
Self {
phase,
label: redact_sensitive_text(&label.into()),
target: redact_sensitive_text(&target.into()),
status: redact_sensitive_text(&status.into()),
item_count,
byte_count,
content: content.map(|text| redact_sensitive_text(&text)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum OutputEvent {
SessionHeader {
session_id: Option<String>,
model: String,
cwd: PathBuf,
},
UserPrompt {
text: String,
},
BashCommand {
command: String,
},
ContextUsage {
current_tokens: usize,
max_tokens: usize,
reasoning_tokens: Option<usize>,
source: ContextUsageSource,
request_sequence: u64,
},
ThinkingSummaryDelta {
text: String,
},
ThinkingSummaryComplete {
text: String,
},
ThinkingSummaryCompleteIdentified {
text: String,
item_id: Option<String>,
turn_id: Option<String>,
},
AssistantDelta {
text: String,
},
AssistantComplete {
text: String,
},
ToolStarted {
call: Box<ToolCall>,
label: String,
},
Diagnostic {
level: String,
message: String,
},
HookDiagnostic {
diagnostic: Box<crate::hooks::HookDiagnostic>,
},
ProviderContextInjection {
metadata: ProviderContextInjectionDisplay,
},
SubdirInstructionInjection {
path: PathBuf,
bytes: usize,
parent_activity_id: Option<String>,
},
ToolResult {
call: Box<ToolCall>,
result: Box<ToolResult>,
summary: Box<ToolDisplaySummary>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ToolDisplaySummary {
pub tool_name: String,
pub status: ToolStatus,
pub unicode_mark: &'static str,
pub ascii_mark: &'static str,
pub label: String,
pub metadata: Vec<(String, String)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ToolStatus {
Running,
Writing,
Success,
Failure,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct ActivityId(pub(crate) String);
impl ActivityId {
pub(crate) fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ActivityKind {
Tool,
Hook,
ProviderContextInjection,
SubagentBatch,
SubagentTask,
Assistant,
Diagnostic,
Compaction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ActivityStatus {
Queued,
Running,
Writing,
Success,
Failed,
Canceled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ActivityMetadata {
pub(crate) label: String,
pub(crate) detail: Option<String>,
pub(crate) fields: Vec<(String, String)>,
}
impl ActivityMetadata {
pub(crate) fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
detail: None,
fields: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ActivityEvent {
Started {
id: ActivityId,
parent_id: Option<ActivityId>,
kind: ActivityKind,
status: ActivityStatus,
metadata: ActivityMetadata,
},
Delta {
id: ActivityId,
preview: String,
},
UsageUpdate {
id: ActivityId,
current_tokens: usize,
max_tokens: usize,
reasoning_tokens: Option<usize>,
source: ContextUsageSource,
request_sequence: u64,
},
FinalPreview {
id: ActivityId,
preview: String,
metadata: Option<ActivityMetadata>,
status: Option<ActivityStatus>,
},
Finished {
id: ActivityId,
status: ActivityStatus,
metadata: Option<ActivityMetadata>,
},
}
pub(crate) type ActivitySender = std::sync::Arc<dyn Fn(ActivityEvent) + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InvocationMode {
Print,
Shell,
MissionControl,
Subagent,
}
impl InvocationMode {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Print => "print",
Self::Shell => "shell",
Self::MissionControl => "mission_control",
Self::Subagent => "subagent",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HookContextMetadata {
pub(crate) session_id: Option<String>,
pub(crate) session_path: Option<PathBuf>,
pub(crate) provider_id: Option<String>,
pub(crate) model_id: Option<String>,
pub(crate) agent_id: Option<String>,
pub(crate) invocation_mode: InvocationMode,
pub(crate) turn_id: Option<String>,
pub(crate) message_id: Option<String>,
pub(crate) subagent: bool,
}
impl HookContextMetadata {
pub(crate) fn new(invocation_mode: InvocationMode) -> Self {
Self {
session_id: None,
session_path: None,
provider_id: None,
model_id: None,
agent_id: None,
invocation_mode,
turn_id: None,
message_id: None,
subagent: matches!(invocation_mode, InvocationMode::Subagent),
}
}
pub(crate) fn for_tool_call(
&self,
turn_id: impl Into<String>,
message_id: impl Into<String>,
) -> Self {
let mut cloned = self.clone();
cloned.turn_id = Some(turn_id.into());
cloned.message_id = Some(message_id.into());
cloned
}
}
#[derive(Clone)]
pub(crate) struct ToolDispatchContext {
pub(crate) parent_activity_id: Option<ActivityId>,
pub(crate) activity_sender: Option<ActivitySender>,
pub(crate) hook_context: HookContextMetadata,
pub(crate) cancellation: AgentCancellation,
}
impl ToolDispatchContext {
pub(crate) fn new(
parent_activity_id: Option<ActivityId>,
activity_sender: Option<ActivitySender>,
) -> Self {
Self::new_with_hook_context(
parent_activity_id,
activity_sender,
HookContextMetadata::new(InvocationMode::Print),
)
}
pub(crate) fn new_with_hook_context(
parent_activity_id: Option<ActivityId>,
activity_sender: Option<ActivitySender>,
hook_context: HookContextMetadata,
) -> Self {
Self::new_with_hook_context_and_cancellation(
parent_activity_id,
activity_sender,
hook_context,
AgentCancellation::default(),
)
}
pub(crate) fn new_with_hook_context_and_cancellation(
parent_activity_id: Option<ActivityId>,
activity_sender: Option<ActivitySender>,
hook_context: HookContextMetadata,
cancellation: AgentCancellation,
) -> Self {
Self {
parent_activity_id,
activity_sender,
hook_context,
cancellation,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutputMode {
Plain,
Rich(RichStyle),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RichStyle {
pub color: bool,
pub unicode: bool,
}
pub(crate) fn output_mode(is_tty: bool, color_enabled: bool, unicode_enabled: bool) -> OutputMode {
if is_tty || color_enabled {
OutputMode::Rich(RichStyle {
color: color_enabled,
unicode: unicode_enabled,
})
} else {
OutputMode::Plain
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_mode_uses_rich_for_tty_or_forced_color() {
assert_eq!(output_mode(false, false, false), OutputMode::Plain);
assert_eq!(
output_mode(false, true, false),
OutputMode::Rich(RichStyle {
color: true,
unicode: false
})
);
assert_eq!(
output_mode(true, true, true),
OutputMode::Rich(RichStyle {
color: true,
unicode: true
})
);
assert_eq!(
output_mode(true, false, true),
OutputMode::Rich(RichStyle {
color: false,
unicode: true
})
);
}
}