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};
14
15/// Default maximum agentic loop iterations when [`Session::max_steps`] is
16/// `None`.
17pub const DEFAULT_MAX_STEPS: usize = 250;
18
19/// An image attachment to include with a user message (e.g. pasted from the
20/// clipboard in the TUI).
21///
22/// # Examples
23///
24/// ```rust
25/// use codetether_agent::session::ImageAttachment;
26///
27/// let img = ImageAttachment {
28/// data_url: "data:image/png;base64,iVBORw0KGgo...".to_string(),
29/// mime_type: Some("image/png".to_string()),
30/// };
31/// assert!(img.data_url.starts_with("data:image/png;base64"));
32/// assert_eq!(img.mime_type.as_deref(), Some("image/png"));
33/// ```
34#[derive(Debug, Clone)]
35pub struct ImageAttachment {
36 /// Base64-encoded data URL, e.g. `"data:image/png;base64,iVBOR..."`.
37 pub data_url: String,
38 /// MIME type of the image, e.g. `"image/png"`.
39 pub mime_type: Option<String>,
40}
41
42/// A conversation session.
43///
44/// See the [`session`](crate::session) module docs for a usage overview.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Session {
47 /// UUID identifying this session on disk.
48 pub id: String,
49 /// Optional human-readable title. Auto-generated from the first user
50 /// message when absent; see [`Session::generate_title`](crate::session::Session::generate_title).
51 pub title: Option<String>,
52 /// When the session was first created.
53 pub created_at: DateTime<Utc>,
54 /// When the session was last modified.
55 pub updated_at: DateTime<Utc>,
56 /// Ordered conversation transcript.
57 pub messages: Vec<Message>,
58 /// Per-tool-call audit records.
59 pub tool_uses: Vec<ToolUse>,
60 /// Aggregate token usage across all completions in this session.
61 pub usage: Usage,
62 /// Name of the agent persona that owns this session.
63 pub agent: String,
64 /// Durable session configuration.
65 pub metadata: SessionMetadata,
66 /// Maximum agentic loop steps. [`None`] falls back to
67 /// [`DEFAULT_MAX_STEPS`].
68 #[serde(skip)]
69 pub max_steps: Option<usize>,
70 /// Optional bus for publishing agent thinking/reasoning.
71 #[serde(skip)]
72 pub bus: Option<Arc<crate::bus::AgentBus>>,
73}
74
75/// Persistent, user-facing configuration for a [`Session`].
76#[derive(Clone, Default, Serialize, Deserialize)]
77pub struct SessionMetadata {
78 /// Workspace directory the session operates in.
79 pub directory: Option<PathBuf>,
80 /// Model selector in `"provider/model"` or `"model"` form.
81 pub model: Option<String>,
82 /// Optional snapshot of the workspace knowledge graph.
83 pub knowledge_snapshot: Option<PathBuf>,
84 /// Execution provenance for audit/traceability.
85 pub provenance: Option<ExecutionProvenance>,
86 /// When true, pending edit previews are auto-confirmed in the TUI.
87 #[serde(default)]
88 pub auto_apply_edits: bool,
89 /// When true, network-reaching tools are allowed.
90 #[serde(default)]
91 pub allow_network: bool,
92 /// When true, the TUI shows slash-command autocomplete.
93 #[serde(default = "default_slash_autocomplete")]
94 pub slash_autocomplete: bool,
95 /// When true, the CLI runs inside an isolated git worktree.
96 #[serde(default = "default_use_worktree")]
97 pub use_worktree: bool,
98 /// Whether this session has been shared publicly.
99 pub shared: bool,
100 /// Public share URL, if any.
101 pub share_url: Option<String>,
102 /// RLM (Recursive Language Model) settings active for this session.
103 ///
104 /// Seeded from [`crate::config::Config::rlm`] at session creation
105 /// (via [`crate::session::Session::apply_config`]) and persisted on
106 /// disk so subsequent runs honour the same thresholds, models, and
107 /// iteration limits. Existing sessions without this field load with
108 /// [`crate::rlm::RlmConfig::default`].
109 #[serde(default)]
110 pub rlm: crate::rlm::RlmConfig,
111 /// Pre-resolved subcall provider from
112 /// [`RlmConfig::subcall_model`](crate::rlm::RlmConfig::subcall_model).
113 ///
114 /// Not serialised — re-resolved from the provider registry each time
115 /// [`Session::apply_config`] runs. When `None`, all RLM iterations
116 /// use the root provider.
117 #[serde(skip)]
118 pub subcall_provider: Option<std::sync::Arc<dyn crate::provider::Provider>>,
119 /// Model name resolved alongside [`Self::subcall_provider`].
120 /// Not serialised.
121 #[serde(skip)]
122 pub subcall_model_name: Option<String>,
123}
124
125impl std::fmt::Debug for SessionMetadata {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.debug_struct("SessionMetadata")
128 .field("directory", &self.directory)
129 .field("model", &self.model)
130 .field("knowledge_snapshot", &self.knowledge_snapshot)
131 .field("provenance", &self.provenance)
132 .field("auto_apply_edits", &self.auto_apply_edits)
133 .field("allow_network", &self.allow_network)
134 .field("slash_autocomplete", &self.slash_autocomplete)
135 .field("use_worktree", &self.use_worktree)
136 .field("shared", &self.shared)
137 .field("share_url", &self.share_url)
138 .field("rlm", &self.rlm)
139 .field(
140 "subcall_provider",
141 &self.subcall_provider.as_ref().map(|_| "<provider>"),
142 )
143 .field("subcall_model_name", &self.subcall_model_name)
144 .finish()
145 }
146}
147
148fn default_slash_autocomplete() -> bool {
149 true
150}
151
152fn default_use_worktree() -> bool {
153 true
154}