1use std::collections::BTreeMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use starweaver_core::{AgentId, ConversationId, Metadata, RunId, SessionId, TaskId, TraceContext};
9use starweaver_model::{ContentPart, ModelMessage, ToolReturnPart};
10use starweaver_usage::{Usage, UsageSnapshotEntry};
11
12use crate::{AgentInfo, MessageBus, ModelConfig, SecurityConfig, StateStore, ToolConfig};
13
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
16pub enum ResumableExportMode {
17 #[default]
19 Curated,
20 Full,
22}
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct ResumableExportOptions {
27 mode: ResumableExportMode,
28 pub include_subagent: bool,
30 pub include_usage_ledger: bool,
32}
33
34impl Default for ResumableExportOptions {
35 fn default() -> Self {
36 Self::curated()
37 }
38}
39
40impl ResumableExportOptions {
41 #[must_use]
43 pub const fn full() -> Self {
44 Self {
45 mode: ResumableExportMode::Full,
46 include_subagent: true,
47 include_usage_ledger: true,
48 }
49 }
50
51 #[must_use]
53 pub const fn curated() -> Self {
54 Self {
55 mode: ResumableExportMode::Curated,
56 include_subagent: true,
57 include_usage_ledger: false,
58 }
59 }
60
61 #[must_use]
63 pub const fn with_usage_ledger(mut self, include_usage_ledger: bool) -> Self {
64 self.include_usage_ledger = include_usage_ledger;
65 self
66 }
67
68 #[must_use]
70 pub const fn with_subagent(mut self, include_subagent: bool) -> Self {
71 self.include_subagent = include_subagent;
72 self
73 }
74
75 #[must_use]
77 pub const fn include_subagent(self) -> bool {
78 self.include_subagent
79 }
80
81 #[must_use]
83 pub const fn include_usage_ledger(self) -> bool {
84 self.include_usage_ledger
85 }
86
87 #[must_use]
89 pub const fn include_starweaver_extensions(self) -> bool {
90 matches!(self.mode, ResumableExportMode::Full)
91 }
92
93 #[must_use]
95 pub const fn include_runtime_policy(self) -> bool {
96 matches!(self.mode, ResumableExportMode::Full)
97 }
98}
99
100#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
102pub struct ResumableState {
103 pub agent_id: AgentId,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub run_id: Option<RunId>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub session_id: Option<SessionId>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub parent_run_id: Option<RunId>,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub parent_task_id: Option<TaskId>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub conversation_id: Option<ConversationId>,
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
122 pub message_history: Vec<ModelMessage>,
123 #[serde(default, skip_serializing_if = "Vec::is_empty")]
125 pub pending_tool_returns: Vec<ToolReturnPart>,
126 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
128 pub subagent_history: BTreeMap<String, Vec<ModelMessage>>,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub user_prompts: Option<Vec<ContentPart>>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub previous_assistant_response_reference: Option<String>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub steering_messages: Vec<String>,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub handoff_message: Option<String>,
141 #[serde(default, skip_serializing)]
143 pub shell_env: BTreeMap<String, String>,
144 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
146 pub deferred_tool_metadata: BTreeMap<String, Metadata>,
147 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
149 pub agent_registry: BTreeMap<String, AgentInfo>,
150 #[serde(default, skip_serializing)]
152 pub approval_required_tools: Vec<String>,
153 #[serde(default, skip_serializing)]
155 pub approval_required_mcp_servers: Vec<String>,
156 #[serde(default, skip_serializing_if = "SecurityConfig::is_default")]
158 pub security: SecurityConfig,
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
163 pub auto_load_files: Vec<String>,
164 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
166 pub tasks: BTreeMap<String, Value>,
167 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
169 pub notes: BTreeMap<String, String>,
170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub tool_search_loaded_tools: Vec<String>,
173 #[serde(default, skip_serializing_if = "Vec::is_empty")]
175 pub tool_search_loaded_namespaces: Vec<String>,
176
177 #[serde(default, skip_serializing_if = "Usage::is_empty")]
179 pub usage: Usage,
180 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
182 pub usage_snapshot_entries: BTreeMap<String, UsageSnapshotEntry>,
183 #[serde(default, skip_serializing_if = "ModelConfig::is_default")]
185 pub model_config: ModelConfig,
186 #[serde(default, skip_serializing_if = "ToolConfig::is_default")]
188 pub tool_config: ToolConfig,
189 #[serde(default = "Utc::now", skip_serializing_if = "is_default_started_at")]
191 pub started_at: DateTime<Utc>,
192 #[serde(default, skip_serializing_if = "StateStore::is_empty")]
194 pub state: StateStore,
195 #[serde(default, skip_serializing_if = "MessageBus::is_empty")]
197 pub message_bus: MessageBus,
198 #[serde(default, skip_serializing_if = "TraceContext::is_empty")]
200 pub trace_snapshot: TraceContext,
201 #[serde(default, skip_serializing_if = "Metadata::is_empty")]
203 pub metadata: Metadata,
204 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
206 pub extra: BTreeMap<String, Value>,
207}
208
209impl starweaver_core::VersionedRecord for ResumableState {
210 const SCHEMA: &'static str = "starweaver.context.resumable_state";
211 const ALLOW_BARE_V0: bool = true;
212}
213
214const fn is_default_started_at(value: &DateTime<Utc>) -> bool {
215 value.timestamp() == 0 && value.timestamp_subsec_nanos() == 0
216}