Skip to main content

codetether_agent/session/
types.rs

1//! Core data types for [`Session`]: the session struct itself, its
2//! persistent metadata, and the image attachment helper used by multimodal
3//! prompts.
4
5use std::path::PathBuf;
6use std::sync::Arc;
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::agent::ToolUse;
12use crate::provenance::ExecutionProvenance;
13use crate::provider::{Message, Usage};
14use crate::session::delegation::DelegationState;
15use crate::session::derive_policy::DerivePolicy;
16use crate::session::history_sink::HistorySinkConfig;
17use crate::session::index::SummaryIndex;
18use crate::session::pages::PageKind;
19
20/// Default maximum agentic loop iterations when [`Session::max_steps`] is
21/// `None`.
22pub const DEFAULT_MAX_STEPS: usize = 250;
23
24/// An image attachment to include with a user message (e.g. pasted from the
25/// clipboard in the TUI).
26///
27/// # Examples
28///
29/// ```rust
30/// use codetether_agent::session::ImageAttachment;
31///
32/// let img = ImageAttachment {
33///     data_url: "data:image/png;base64,iVBORw0KGgo...".to_string(),
34///     mime_type: Some("image/png".to_string()),
35/// };
36/// assert!(img.data_url.starts_with("data:image/png;base64"));
37/// assert_eq!(img.mime_type.as_deref(), Some("image/png"));
38/// ```
39#[derive(Debug, Clone)]
40pub struct ImageAttachment {
41    /// Base64-encoded data URL, e.g. `"data:image/png;base64,iVBOR..."`.
42    pub data_url: String,
43    /// MIME type of the image, e.g. `"image/png"`.
44    pub mime_type: Option<String>,
45}
46
47/// A conversation session.
48///
49/// See the [`session`](crate::session) module docs for a usage overview.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Session {
52    /// UUID identifying this session on disk.
53    pub id: String,
54    /// Optional human-readable title. Auto-generated from the first user
55    /// message when absent; see [`Session::generate_title`](crate::session::Session::generate_title).
56    pub title: Option<String>,
57    /// When the session was first created.
58    pub created_at: DateTime<Utc>,
59    /// When the session was last modified.
60    pub updated_at: DateTime<Utc>,
61    /// Durable session configuration.
62    ///
63    /// Serialized *before* [`Self::messages`] so cheap workspace-match
64    /// prefiltering (see [`crate::session::header::SessionHeader`]) can
65    /// avoid lexing past the transcript.
66    pub metadata: SessionMetadata,
67    /// Name of the agent persona that owns this session.
68    pub agent: String,
69    /// Ordered conversation transcript.
70    #[serde(deserialize_with = "crate::session::tail_seed::deserialize_tail_vec")]
71    pub messages: Vec<Message>,
72    /// Per-message page classification sidecar.
73    ///
74    /// Backfilled on load for legacy sessions that predate the
75    /// history/context split.
76    #[serde(default)]
77    pub pages: Vec<PageKind>,
78    /// Hierarchical summary cache — Phase B step 14. Populated lazily
79    /// by step 18's producer and invalidated on the
80    /// [`Session::add_message`] hot path.
81    #[serde(default)]
82    pub summary_index: SummaryIndex,
83    /// Per-tool-call audit records.
84    #[serde(deserialize_with = "crate::session::tail_seed::deserialize_tail_vec")]
85    pub tool_uses: Vec<ToolUse>,
86    /// Aggregate token usage across all completions in this session.
87    pub usage: Usage,
88    /// Maximum agentic loop steps. [`None`] falls back to
89    /// [`DEFAULT_MAX_STEPS`].
90    #[serde(skip)]
91    pub max_steps: Option<usize>,
92    /// Optional bus for publishing agent thinking/reasoning.
93    #[serde(skip)]
94    pub bus: Option<Arc<crate::bus::AgentBus>>,
95}
96
97/// Persistent, user-facing configuration for a [`Session`].
98#[derive(Clone, Default, Serialize, Deserialize)]
99pub struct SessionMetadata {
100    /// Workspace directory the session operates in.
101    pub directory: Option<PathBuf>,
102    /// Model selector in `"provider/model"` or `"model"` form.
103    pub model: Option<String>,
104    /// Optional snapshot of the workspace knowledge graph.
105    pub knowledge_snapshot: Option<PathBuf>,
106    /// Execution provenance for audit/traceability.
107    pub provenance: Option<ExecutionProvenance>,
108    /// When true, pending edit previews are auto-confirmed in the TUI.
109    #[serde(default)]
110    pub auto_apply_edits: bool,
111    /// When true, network-reaching tools are allowed.
112    #[serde(default)]
113    pub allow_network: bool,
114    /// When true, the TUI shows slash-command autocomplete.
115    #[serde(default = "default_slash_autocomplete")]
116    pub slash_autocomplete: bool,
117    /// When true, the CLI runs inside an isolated git worktree.
118    #[serde(default = "default_use_worktree")]
119    pub use_worktree: bool,
120    /// Whether this session has been shared publicly.
121    pub shared: bool,
122    /// Public share URL, if any.
123    pub share_url: Option<String>,
124    /// RLM (Recursive Language Model) settings active for this session.
125    ///
126    /// Seeded from [`crate::config::Config::rlm`] at session creation
127    /// (via [`crate::session::Session::apply_config`]) and persisted on
128    /// disk so subsequent runs honour the same thresholds, models, and
129    /// iteration limits. Existing sessions without this field load with
130    /// [`crate::rlm::RlmConfig::default`].
131    #[serde(default)]
132    pub rlm: crate::rlm::RlmConfig,
133    /// Per-session context derivation policy.
134    ///
135    /// Defaults to [`DerivePolicy::Legacy`]. The prompt loop can still
136    /// override this at runtime via `CODETETHER_CONTEXT_POLICY`.
137    #[serde(default)]
138    pub context_policy: DerivePolicy,
139    /// CADMAS-CTX routing posteriors for this session.
140    #[serde(default)]
141    pub delegation: DelegationState,
142    /// Resumable run checkpoint data, persisted beside session metadata.
143    #[serde(default)]
144    pub run_checkpoint: Option<crate::session::RunCheckpoint>,
145    /// Runtime MinIO / S3 history sink configuration.
146    ///
147    /// Intentionally not serialized: carrying live access keys inside the
148    /// session JSON would persist secrets to disk.
149    #[serde(skip)]
150    pub history_sink: Option<HistorySinkConfig>,
151    /// Claim-scoped provider key payload. Not serialized so BYOK secrets are not written to disk.
152    #[serde(skip)]
153    pub provider_keys: Option<serde_json::Value>,
154    /// Pre-resolved subcall provider from
155    /// [`RlmConfig::subcall_model`](crate::rlm::RlmConfig::subcall_model).
156    ///
157    /// Not serialised — re-resolved from the provider registry each time
158    /// [`Session::apply_config`] runs. When `None`, all RLM iterations
159    /// use the root provider.
160    #[serde(skip)]
161    pub subcall_provider: Option<std::sync::Arc<dyn crate::provider::Provider>>,
162    /// Model name resolved alongside [`Self::subcall_provider`].
163    /// Not serialised.
164    #[serde(skip)]
165    pub subcall_model_name: Option<String>,
166}
167
168impl std::fmt::Debug for SessionMetadata {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("SessionMetadata")
171            .field("directory", &self.directory)
172            .field("model", &self.model)
173            .field("knowledge_snapshot", &self.knowledge_snapshot)
174            .field("provenance", &self.provenance)
175            .field("auto_apply_edits", &self.auto_apply_edits)
176            .field("allow_network", &self.allow_network)
177            .field("slash_autocomplete", &self.slash_autocomplete)
178            .field("use_worktree", &self.use_worktree)
179            .field("shared", &self.shared)
180            .field("share_url", &self.share_url)
181            .field("rlm", &self.rlm)
182            .field("context_policy", &self.context_policy)
183            .field("delegation", &self.delegation)
184            .field("run_checkpoint", &self.run_checkpoint)
185            .field(
186                "history_sink",
187                &self
188                    .history_sink
189                    .as_ref()
190                    .map(|cfg| (&cfg.endpoint, &cfg.bucket, &cfg.prefix)),
191            )
192            .field(
193                "provider_keys",
194                &self.provider_keys.as_ref().map(|_| "<redacted>"),
195            )
196            .field(
197                "subcall_provider",
198                &self.subcall_provider.as_ref().map(|_| "<provider>"),
199            )
200            .field("subcall_model_name", &self.subcall_model_name)
201            .finish()
202    }
203}
204
205fn default_slash_autocomplete() -> bool {
206    true
207}
208
209fn default_use_worktree() -> bool {
210    true
211}