use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExternalJobKind {
VideoGen,
ImageGen,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExternalJobOrigin {
Agent,
Cron,
UserDirect,
}
#[derive(Debug, Clone)]
pub enum PollOutcome {
Pending,
Done(String),
Failed(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExternalJobStatus {
Pending,
Polling,
Done,
Failed,
TimedOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalJobDelivery {
pub channel: String,
pub target_id: String,
pub is_group: bool,
pub reply_to: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalJob {
pub id: String,
pub session_key: String,
pub delivery: ExternalJobDelivery,
pub origin: ExternalJobOrigin,
pub provider: String,
pub external_task_id: String,
pub kind: ExternalJobKind,
pub prompt: String,
pub submitted_at: i64,
pub next_poll_at: i64,
pub poll_count: u32,
pub timeout_at: i64,
pub status: ExternalJobStatus,
pub error: Option<String>,
pub result_url: Option<String>,
pub result_path: Option<String>,
#[serde(default)]
pub delivered_at: Option<i64>,
#[serde(default)]
pub delivery_attempts: u32,
}
pub const DEFAULT_TIMEOUT_SECS: u64 = 30 * 60;
const EARLY_POLL_INTERVAL_SECS: u64 = 10;
const LATE_POLL_INTERVAL_SECS: u64 = 60;
const EARLY_POLL_BUDGET: u32 = 12;
impl ExternalJob {
pub fn new_submitted(
session_key: impl Into<String>,
delivery: ExternalJobDelivery,
origin: ExternalJobOrigin,
provider: impl Into<String>,
external_task_id: impl Into<String>,
kind: ExternalJobKind,
prompt: impl Into<String>,
) -> Self {
let now = chrono::Utc::now().timestamp();
Self {
id: uuid::Uuid::new_v4().to_string(),
session_key: session_key.into(),
delivery,
origin,
provider: provider.into(),
external_task_id: external_task_id.into(),
kind,
prompt: prompt.into(),
submitted_at: now,
next_poll_at: now + EARLY_POLL_INTERVAL_SECS as i64,
poll_count: 0,
timeout_at: now + DEFAULT_TIMEOUT_SECS as i64,
status: ExternalJobStatus::Pending,
error: None,
result_url: None,
result_path: None,
delivered_at: None,
delivery_attempts: 0,
}
}
pub fn needs_delivery(&self) -> bool {
matches!(
self.status,
ExternalJobStatus::Done | ExternalJobStatus::Failed | ExternalJobStatus::TimedOut
) && self.delivered_at.is_none()
}
pub const MAX_DELIVERY_ATTEMPTS: u32 = 60;
pub fn next_poll_delay_secs(&self) -> u64 {
if self.poll_count < EARLY_POLL_BUDGET {
EARLY_POLL_INTERVAL_SECS
} else {
LATE_POLL_INTERVAL_SECS
}
}
pub fn is_timed_out(&self) -> bool {
chrono::Utc::now().timestamp() > self.timeout_at
}
}