use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use starweaver_core::{AgentId, ConversationId, Metadata, RunId, SessionId, TraceContext};
use starweaver_model::{ContentPart, ModelMessage, ToolReturnPart};
use starweaver_usage::{Usage, UsageSnapshotEntry};
use crate::{AgentInfo, MessageBus, ModelConfig, SecurityConfig, StateStore, ToolConfig};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ResumableExportMode {
#[default]
Curated,
Full,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ResumableExportOptions {
mode: ResumableExportMode,
pub include_subagent: bool,
pub include_usage_ledger: bool,
}
impl Default for ResumableExportOptions {
fn default() -> Self {
Self::curated()
}
}
impl ResumableExportOptions {
#[must_use]
pub const fn full() -> Self {
Self {
mode: ResumableExportMode::Full,
include_subagent: true,
include_usage_ledger: true,
}
}
#[must_use]
pub const fn curated() -> Self {
Self {
mode: ResumableExportMode::Curated,
include_subagent: true,
include_usage_ledger: false,
}
}
#[must_use]
pub const fn with_usage_ledger(mut self, include_usage_ledger: bool) -> Self {
self.include_usage_ledger = include_usage_ledger;
self
}
#[must_use]
pub const fn with_subagent(mut self, include_subagent: bool) -> Self {
self.include_subagent = include_subagent;
self
}
#[must_use]
pub const fn include_subagent(self) -> bool {
self.include_subagent
}
#[must_use]
pub const fn include_usage_ledger(self) -> bool {
self.include_usage_ledger
}
#[must_use]
pub const fn include_starweaver_extensions(self) -> bool {
matches!(self.mode, ResumableExportMode::Full)
}
#[must_use]
pub const fn include_runtime_policy(self) -> bool {
matches!(self.mode, ResumableExportMode::Full)
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResumableState {
pub agent_id: AgentId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_id: Option<RunId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<SessionId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub conversation_id: Option<ConversationId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub message_history: Vec<ModelMessage>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub pending_tool_returns: Vec<ToolReturnPart>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub subagent_history: BTreeMap<String, Vec<ModelMessage>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_prompts: Option<Vec<ContentPart>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub previous_assistant_response_reference: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub steering_messages: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub handoff_message: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub shell_env: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub deferred_tool_metadata: BTreeMap<String, Metadata>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub agent_registry: BTreeMap<String, AgentInfo>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub approval_required_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub approval_required_mcp_servers: Vec<String>,
#[serde(default, skip_serializing_if = "SecurityConfig::is_default")]
pub security: SecurityConfig,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub auto_load_files: Vec<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub tasks: BTreeMap<String, Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub notes: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_search_loaded_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_search_loaded_namespaces: Vec<String>,
#[serde(default, skip_serializing_if = "Usage::is_empty")]
pub usage: Usage,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub usage_snapshot_entries: BTreeMap<String, UsageSnapshotEntry>,
#[serde(default, skip_serializing_if = "ModelConfig::is_default")]
pub model_config: ModelConfig,
#[serde(default, skip_serializing_if = "ToolConfig::is_default")]
pub tool_config: ToolConfig,
#[serde(default = "Utc::now", skip_serializing_if = "is_default_started_at")]
pub started_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "StateStore::is_empty")]
pub state: StateStore,
#[serde(default, skip_serializing_if = "MessageBus::is_empty")]
pub message_bus: MessageBus,
#[serde(default, skip_serializing_if = "TraceContext::is_empty")]
pub trace_snapshot: TraceContext,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub extra: BTreeMap<String, Value>,
}
const fn is_default_started_at(value: &DateTime<Utc>) -> bool {
value.timestamp() == 0 && value.timestamp_subsec_nanos() == 0
}