use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum FlowEvent {
FlowStarted {
instance_id: String,
flow_id: String,
flow_name: String,
},
StepStarted {
instance_id: String,
step_id: String,
step_name: String,
step_type: String,
},
StepCompleted {
instance_id: String,
step_id: String,
success: bool,
outputs: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
duration_ms: u64,
},
FlowCompleted {
instance_id: String,
flow_id: String,
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
total_steps: usize,
duration_ms: u64,
},
WaitingForInput {
instance_id: String,
step_id: String,
prompt: String,
options: Vec<String>,
},
ParallelProgress {
instance_id: String,
step_id: String,
completed: usize,
total: usize,
},
FlowPaused {
instance_id: String,
flow_id: String,
step_id: Option<String>,
},
FlowResumed {
instance_id: String,
flow_id: String,
step_id: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "event_type", content = "data")]
pub enum AppEvent {
ExecutorEvent {
event: String,
timestamp: i64,
sequence: u32,
data: Value,
},
ExecutorTreeEvent {
event_type: String,
node: Value,
path: Vec<String>,
timestamp: i64,
sequence: u32,
},
ExecutorError {
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<String>,
},
ExecutorResponse {
id: String,
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
ImageRecognition { data: Value },
ExtractionEvent {
event: String,
timestamp: i64,
sequence: u32,
data: Value,
},
ExtractionError {
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<String>,
},
ExtractionResponse {
id: String,
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
RagProgress {
project_id: String,
status: String,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
percent: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
elements_processed: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
total_elements: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
RagCompletion {
project_id: String,
success: bool,
total_processed: i32,
successful: i32,
failed: i32,
web_sync_success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
web_sync_error: Option<String>,
},
FlowEvent(FlowEvent),
AiOutput {
session_id: String,
content: String,
#[serde(skip_serializing_if = "Option::is_none")]
content_type: Option<String>,
},
FindingDetected { finding: Value },
FindingResolved { finding: Value },
TestNavigation { data: Value },
UiBridgeRequest { data: Value },
OrchestratorStateChange {
task_run_id: String,
workflow_stage: String,
iteration: u32,
phase: String,
#[serde(skip_serializing_if = "Option::is_none")]
state_data: Option<Value>,
},
StepProgress {
task_run_id: String,
step_index: usize,
step_name: String,
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<Value>,
timestamp: i64,
},
TaskRunUpdate {
task_run_id: String,
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
iteration: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<Value>,
timestamp: i64,
},
ApprovalRequired {
task_run_id: String,
approval_id: String,
iteration: u32,
prompt: String,
},
ApprovalResolved {
task_run_id: String,
approval_id: String,
approved: bool,
action: String,
},
DeferredQuestionCreated {
task_run_id: String,
question_id: String,
iteration: u32,
question: String,
confidence: f64,
risk_level: String,
},
DeferredQuestionReviewed {
task_run_id: String,
question_id: String,
status: String,
rework_triggered: bool,
},
CanvasUpdate {
action: String,
panel_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
panel: Option<CanvasPanel>,
#[serde(skip_serializing_if = "Option::is_none")]
task_run_id: Option<String>,
},
AiOutputChunk {
task_run_id: String,
chunk: String,
accumulated_length: usize,
},
IterationMetrics {
task_run_id: String,
iteration: u32,
failed_step_count: u32,
passed_step_count: u32,
skipped_step_count: u32,
new_failures: u32,
repeated_failures: u32,
is_stalled: bool,
},
BlameAttribution {
task_run_id: String,
iteration: u32,
attributed_failures: u32,
oscillating_files: u32,
revert_patterns: u32,
blame_json: String,
},
ConstraintResults {
task_run_id: String,
iteration: u32,
summary: String,
has_blocking: bool,
results: Value,
},
WorkflowQueued {
task_run_id: String,
workflow_name: String,
queue_position: usize,
},
WorkflowDequeued {
task_run_id: String,
workflow_name: String,
wait_time_ms: u64,
},
CostUpdate {
task_run_id: String,
phase: String,
#[serde(skip_serializing_if = "Option::is_none")]
iteration: Option<u32>,
input_tokens: u64,
output_tokens: u64,
cache_creation_tokens: u64,
cache_read_tokens: u64,
cost_usd: f64,
cumulative_cost_usd: f64,
cache_hit_rate: f64,
timestamp: i64,
},
BudgetWarning {
task_run_id: String,
remaining_fraction: f64,
total_cost_usd: f64,
budget_limit_usd: f64,
message: String,
timestamp: i64,
},
CostAnomaly {
task_run_id: String,
cost_usd: f64,
mean_cost_usd: f64,
std_dev: f64,
z_score: f64,
message: String,
timestamp: i64,
},
Error {
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
context: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UiBridgeRequestEnvelope {
pub request_id: String,
#[serde(rename = "type")]
pub request_type: String,
#[serde(flatten)]
pub data: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UiBridgeResponseEnvelope {
pub request_id: String,
#[serde(rename = "type")]
pub request_type: String,
pub success: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<Value>,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CanvasPanel {
pub panel_id: String,
pub component: String,
pub title: String,
pub data: Value,
pub priority: i32,
pub size: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
pub task_run_id: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct UiBridgeHttpErrorEnvelope {
pub success: bool,
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UiBridgeHttpHealthData {
pub responsive: bool,
pub last_heartbeat: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UiBridgeHttpHealthAppInfo {
pub app_id: String,
pub app_name: String,
pub app_type: String,
pub framework: String,
pub capabilities: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UiBridgeHttpHealthEnvelope {
pub success: bool,
pub data: UiBridgeHttpHealthData,
pub timestamp: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ui_bridge: Option<UiBridgeHttpHealthAppInfo>,
}