use serde::Serialize;
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum OutputFormat {
Table,
Json,
}
#[derive(Debug)]
pub struct HistoryOptions {
pub db_path: std::path::PathBuf,
pub format: OutputFormat,
pub state: Option<String>,
pub name: Option<String>,
pub since: Option<i64>,
pub until: Option<i64>,
pub limit: usize,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryRunSummary {
#[serde(rename = "runID")]
pub run_id: String,
pub task_id: String,
#[serde(skip)]
pub worker_id: String,
pub root_run_id: String,
pub state: String,
#[serde(skip)]
pub workflow_name: String,
pub metadata: Value,
pub script_path: Option<String>,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub created_at: i64,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub updated_at: i64,
pub attempts: u32,
pub completed_steps: u32,
pub failed_steps: u32,
pub total_tokens: u64,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryRunDetail {
pub workflow_run: HistoryWorkflowRunResource,
#[serde(skip_serializing_if = "Option::is_none")]
pub results: Option<Value>,
pub token_usage: HistoryTokenUsage,
pub attempts: Vec<HistoryAttempt>,
pub steps: Vec<HistoryStep>,
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryTokenUsage {
pub input_tokens: u64,
pub cache_read_tokens: u64,
pub output_tokens: u64,
pub cache_write_tokens: u64,
pub total_tokens: u64,
pub by_phase: BTreeMap<String, HistoryTokenUsageTotals>,
#[serde(skip)]
pub unphased: HistoryTokenUsageTotals,
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryTokenUsageTotals {
pub input_tokens: u64,
pub cache_read_tokens: u64,
pub output_tokens: u64,
pub cache_write_tokens: u64,
pub total_tokens: u64,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryWorkflowRunResource {
#[serde(rename = "runID")]
pub run_id: String,
pub task_id: String,
pub worker_id: String,
pub root_run_id: String,
pub state: String,
pub metadata: Value,
pub script_path: Option<String>,
pub args: Value,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub created_at: i64,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub updated_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_reason: Option<Value>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryAttempt {
pub attempt_id: String,
pub attempt: u32,
pub state: String,
pub worker_id: String,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub started_at: i64,
#[serde(serialize_with = "serialize_optional_epoch_ms_iso8601")]
pub completed_at: Option<i64>,
pub failure_reason: Option<Value>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryStep {
pub step_id: String,
pub step_kind: String,
pub checkpoint_name: String,
pub state: String,
pub attempts: u32,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub created_at: i64,
#[serde(serialize_with = "serialize_epoch_ms_iso8601")]
pub updated_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent: Option<HistoryStepAgent>,
pub token_usage: HistoryTokenUsageTotals,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryStepAgent {
pub provider: Option<String>,
pub model: Option<String>,
pub phase: Option<String>,
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub isolation: Option<Value>,
}
fn serialize_epoch_ms_iso8601<S>(timestamp_ms: &i64, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&epoch_ms_to_iso8601(*timestamp_ms))
}
fn serialize_optional_epoch_ms_iso8601<S>(
timestamp_ms: &Option<i64>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match timestamp_ms {
Some(timestamp_ms) => serializer.serialize_some(&epoch_ms_to_iso8601(*timestamp_ms)),
None => serializer.serialize_none(),
}
}
pub fn epoch_ms_to_iso8601(timestamp_ms: i64) -> String {
let seconds = timestamp_ms.div_euclid(1000);
let millis = timestamp_ms.rem_euclid(1000);
let days = seconds.div_euclid(86_400);
let seconds_of_day = seconds.rem_euclid(86_400);
let (year, month, day) = civil_from_days(days);
let hour = seconds_of_day / 3600;
let minute = (seconds_of_day % 3600) / 60;
let second = seconds_of_day % 60;
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z")
}
fn civil_from_days(days_since_unix_epoch: i64) -> (i64, i64, i64) {
let z = days_since_unix_epoch + 719_468;
let era = if z >= 0 { z } else { z - 146_096 }.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096).div_euclid(365);
let mut year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2).div_euclid(153);
let day = doy - (153 * mp + 2).div_euclid(5) + 1;
let month = mp + if mp < 10 { 3 } else { -9 };
if month <= 2 {
year += 1;
}
(year, month, day)
}