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