use serde::{Deserialize, Serialize};
use super::scalar::{AttemptId, BoundedJson, DeliveryId, FiniteF64, SignalId, TaskId, WireU64};
use super::syscall::SyscallRequest;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ExternalEvent {
DeliverSignal(DeliverSignal),
ChildCompleted(ChildCompleted),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DeliverSignal {
pub delivery_id: DeliveryId,
pub attempt: u32,
pub signal: LogicalSignal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogicalSignal {
pub signal_id: SignalId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<SignalSourceKind>,
#[serde(default)]
pub target: SignalTarget,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub urgency: Option<SignalUrgency>,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub payload: BoundedJson,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_timestamp_ms: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dedupe_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub escalate_after_ms: Option<WireU64>,
}
impl LogicalSignal {
pub fn new(signal_id: SignalId) -> Self {
Self {
signal_id,
source: None,
target: SignalTarget::default(),
urgency: None,
payload: BoundedJson::null(),
source_timestamp_ms: None,
dedupe_key: None,
escalate_after_ms: None,
}
}
pub fn effective_urgency(&self, escalation_enabled: bool) -> SignalUrgency {
let urgency = self.urgency.unwrap_or(SignalUrgency::Normal);
let due_on_admission = self.escalate_after_ms.is_some_and(|after| after.get() == 0);
if escalation_enabled && due_on_admission {
urgency.escalated()
} else {
urgency
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SignalTarget {
Operation(OperationTarget),
Task(TaskTarget),
}
impl Default for SignalTarget {
fn default() -> Self {
Self::Operation(OperationTarget {})
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OperationTarget {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskTarget {
pub task_id: TaskId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SignalSourceKind {
Cron,
Gateway,
Heartbeat,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SignalUrgency {
Low,
Normal,
High,
Critical,
}
impl SignalUrgency {
pub fn escalated(self) -> Self {
match self {
Self::Low => Self::Normal,
Self::Normal => Self::High,
Self::High | Self::Critical => Self::Critical,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChildCompleted {
pub task_id: TaskId,
pub attempt_id: AttemptId,
pub result: ChildResult,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub parent_requests: Vec<SyscallRequest>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChildResult {
#[serde(default)]
pub status: ChildStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<UsageFacts>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub score: Option<FiniteF64>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChildStatus {
#[default]
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UsageFacts {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turns: Option<u32>,
}