use serde::{Deserialize, Serialize};
pub mod glyph {
pub const BROWSER: &str = "↗";
pub const ERROR: &str = "!";
pub const EXEC: &str = "•";
pub const PATCH: &str = "∂";
pub const PLAN: &str = "∷";
pub const WORKFLOW: &str = "⌘";
}
pub const EXPLORED_STABLE_ID: &str = "explored";
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum ToolUiEvent {
Exec(ToolUiData),
Terminal(TerminalUiData),
CodingOpenProject(CodingOpenProjectUiData),
Explored(ExploredUiData),
CodingEdit(CodingEditUiData),
CodingReview(CodingReviewUiData),
Browser(BrowserUiData),
Patch(PatchUiData),
Telegram(TelegramUiData),
Reply(ReplyUiData),
Plan(PlanUiData),
WebSearch(WebSearchUiData),
CreatePrimitiveSpec(CreatePrimitiveSpecUiData),
ActivatePrimitive(ActivatePrimitiveUiData),
#[serde(alias = "Finish", alias = "Work")]
App(ToolUiData),
Warning(ToolUiData),
Error(ToolUiData),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind")]
pub enum ToolCallUiEvent {
Exec(ToolUiData),
Terminal(TerminalUiData),
Browser(BrowserUiData),
Patch(PatchUiData),
CodingEdit(CodingEditUiData),
Telegram(TelegramUiData),
Plan(PlanUiData),
CreatePrimitiveSpec(ToolUiData),
ActivatePrimitive(ToolUiData),
#[serde(alias = "Finish", alias = "Work")]
App(ToolUiData),
Error(ToolUiData),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ToolUiData {
pub title: String,
#[serde(default)]
pub body_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TerminalUiData {
pub action: TerminalUiAction,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin: Option<TerminalUiOrigin>,
pub title: String,
#[serde(default)]
pub body_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BrowserUiData {
pub action: BrowserUiAction,
pub title: String,
#[serde(default)]
pub body_lines: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ref_count: Option<usize>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct WebSearchUiData {
pub action: WebSearchUiAction,
pub query: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default)]
pub body_lines: Vec<String>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WebSearchUiAction {
Searching,
Searched,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CodingOpenProjectUiData {
pub project_root: String,
#[serde(default)]
pub detail_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ExploredUiData {
pub stable_id: String,
pub title: String,
#[serde(default)]
pub calls: Vec<ExploredCallUiData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ExploredCallUiData {
pub tool_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub action: Option<ExploredCallUiAction>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secondary_target: Option<String>,
pub summary: String,
#[serde(default)]
pub detail_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ExploredCallUiAction {
Read,
List,
Search,
Run,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CodingEditUiData {
pub stable_id: String,
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_app: Option<String>,
pub selector: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<String>,
pub added_lines: usize,
pub removed_lines: usize,
pub propagation_count: usize,
#[serde(default)]
pub impact_lines: Vec<String>,
#[serde(default)]
pub diff_files: Vec<PatchFileUiData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CodingReviewUiData {
pub title: String,
pub summary: String,
pub review_pending: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TerminalUiAction {
Execute,
Continue,
Poll,
Terminate,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TerminalUiOrigin {
Agent,
User,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BrowserUiAction {
OpenPage,
Snapshot,
Wait,
Click,
Fill,
Back,
Forward,
Reload,
ClosePage,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchUiData {
pub summary_line: String,
#[serde(default)]
pub files: Vec<PatchFileUiData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchFileUiData {
pub path: String,
pub operation: PatchFileOperation,
pub added_lines: usize,
pub removed_lines: usize,
#[serde(default)]
pub diff_lines: Vec<PatchDiffLineUiData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PatchFileOperation {
Add,
Delete,
Update,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PatchDiffLineUiData {
pub kind: PatchDiffLineKind,
pub old_lineno: Option<usize>,
pub new_lineno: Option<usize>,
pub text: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PatchDiffLineKind {
Context,
Delete,
Add,
HunkBreak,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct TelegramUiData {
pub action: TelegramUiAction,
pub title: String,
#[serde(default)]
pub detail_lines: Vec<String>,
#[serde(default)]
pub message_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReplyUiData {
pub disposition: ReplyDisposition,
#[serde(default)]
pub subject: ReplySubject,
#[serde(default)]
pub message_lines: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlanUiData {
#[serde(default)]
pub kind: PlanUiKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub explanation: Option<String>,
pub steps: Vec<PlanStepUiData>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum PlanUiKind {
Proposed,
#[default]
Updated,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PlanStepUiData {
pub status: PlanStepUiStatus,
pub text: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PlanStepUiStatus {
Pending,
InProgress,
Completed,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreatePrimitiveSpecUiData {
pub primitive_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ActivatePrimitiveUiData {
pub primitive_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReplyDisposition {
Resolved,
Dismissed,
Failed,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReplySubject {
#[default]
Message,
Notice,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TelegramUiAction {
ListChats,
#[serde(alias = "ReadChat")]
ReadHistory,
SelectChat,
SendMessage,
ResolveChat,
}
impl ToolUiEvent {
pub fn terminal(
action: TerminalUiAction,
title: impl Into<String>,
body_lines: Vec<String>,
) -> Self {
Self::terminal_with_origin(action, None, title, body_lines)
}
pub fn terminal_with_origin(
action: TerminalUiAction,
origin: Option<TerminalUiOrigin>,
title: impl Into<String>,
body_lines: Vec<String>,
) -> Self {
Self::Terminal(TerminalUiData {
action,
origin,
title: title.into(),
body_lines,
})
}
pub fn coding_open_project(project_root: impl Into<String>, detail_lines: Vec<String>) -> Self {
Self::CodingOpenProject(CodingOpenProjectUiData {
project_root: project_root.into(),
detail_lines,
})
}
pub fn explored(
stable_id: impl Into<String>,
title: impl Into<String>,
calls: Vec<ExploredCallUiData>,
) -> Self {
Self::Explored(ExploredUiData {
stable_id: stable_id.into(),
title: title.into(),
calls,
})
}
pub fn coding_edit(data: CodingEditUiData) -> Self {
Self::CodingEdit(data)
}
pub fn coding_review(
title: impl Into<String>,
summary: impl Into<String>,
review_pending: bool,
) -> Self {
Self::CodingReview(CodingReviewUiData {
title: title.into(),
summary: summary.into(),
review_pending,
})
}
pub fn plan(steps: Vec<PlanStepUiData>) -> Self {
Self::Plan(PlanUiData {
kind: PlanUiKind::Updated,
explanation: None,
steps,
})
}
pub fn plan_with_explanation(explanation: Option<String>, steps: Vec<PlanStepUiData>) -> Self {
Self::Plan(PlanUiData {
kind: PlanUiKind::Updated,
explanation,
steps,
})
}
pub fn create_primitive_spec(primitive_id: impl Into<String>) -> Self {
Self::CreatePrimitiveSpec(CreatePrimitiveSpecUiData {
primitive_id: primitive_id.into(),
})
}
pub fn activate_primitive(primitive_id: impl Into<String>) -> Self {
Self::ActivatePrimitive(ActivatePrimitiveUiData {
primitive_id: primitive_id.into(),
})
}
pub fn reply(disposition: ReplyDisposition, message_lines: Vec<String>) -> Self {
Self::Reply(ReplyUiData {
disposition,
subject: ReplySubject::Message,
message_lines,
})
}
pub fn notice_reply(disposition: ReplyDisposition, message_lines: Vec<String>) -> Self {
Self::Reply(ReplyUiData {
disposition,
subject: ReplySubject::Notice,
message_lines,
})
}
pub fn app(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::App(ToolUiData {
title: title.into(),
body_lines,
})
}
pub fn error(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::Error(ToolUiData {
title: title.into(),
body_lines,
})
}
}
impl ToolCallUiEvent {
pub fn coding_edit(data: CodingEditUiData) -> Self {
Self::CodingEdit(data)
}
pub fn terminal(
action: TerminalUiAction,
title: impl Into<String>,
body_lines: Vec<String>,
) -> Self {
Self::Terminal(TerminalUiData {
action,
origin: None,
title: title.into(),
body_lines,
})
}
pub fn plan(data: PlanUiData) -> Self {
Self::Plan(data)
}
pub fn create_primitive_spec(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::CreatePrimitiveSpec(ToolUiData {
title: title.into(),
body_lines,
})
}
pub fn activate_primitive(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::ActivatePrimitive(ToolUiData {
title: title.into(),
body_lines,
})
}
pub fn app(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::App(ToolUiData {
title: title.into(),
body_lines,
})
}
pub fn error(title: impl Into<String>, body_lines: Vec<String>) -> Self {
Self::Error(ToolUiData {
title: title.into(),
body_lines,
})
}
}
pub fn compact_body_lines(text: &str, max_lines: usize) -> Vec<String> {
text.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.take(max_lines)
.map(ToString::to_string)
.collect()
}