返回 DeepSeek-TUI-2026
session.rs
根目录 / crates / tui / src / core / session.rs
1 //! Session state management for the core engine.
2 //!
3 //! Tracks conversation history, token usage, and session metadata.
4
5 use crate::cycle_manager::CycleBriefing;
6 use crate::models::{Message, SystemPrompt, Usage};
7 use crate::project_context::{ProjectContext, load_project_context_with_parents};
8 use crate::tui::approval::ApprovalMode;
9 use crate::working_set::WorkingSet;
10 use chrono::{DateTime, Utc};
11 use std::path::PathBuf;
12
13 /// Session state for the engine.
14 #[derive(Debug, Clone)]
15 pub struct Session {
16 /// Model being used
17 pub model: String,
18
19 /// Reasoning-effort tier for DeepSeek thinking mode:
20 /// `"off" | "low" | "medium" | "high" | "max"`. `None` lets the provider
21 /// apply its own defaults.
22 pub reasoning_effort: Option<String>,
23 /// Whether the user selected automatic reasoning effort.
24 pub reasoning_effort_auto: bool,
25
26 /// Whether the user selected automatic model routing.
27 pub auto_model: bool,
28
29 /// Workspace directory
30 pub workspace: PathBuf,
31
32 /// System prompt (optional)
33 pub system_prompt: Option<SystemPrompt>,
34 /// Hash of the last assembled stable system prompt. Used to avoid
35 /// replacing `system_prompt` when unchanged.
36 pub last_system_prompt_hash: Option<u64>,
37 /// Persisted summary blocks generated by context compaction.
38 pub compaction_summary_prompt: Option<SystemPrompt>,
39
40 /// Conversation history (API format)
41 pub messages: Vec<Message>,
42
43 /// Total tokens used in this session
44 pub total_usage: SessionUsage,
45
46 /// Whether shell execution is allowed
47 pub allow_shell: bool,
48
49 /// Whether to trust paths outside workspace
50 pub trust_mode: bool,
51
52 /// Whether the current session should auto-approve tool safety checks.
53 pub auto_approve: bool,
54
55 /// Live UI approval policy used to steer the system prompt.
56 pub approval_mode: ApprovalMode,
57
58 /// Notes file path
59 pub notes_path: PathBuf,
60
61 /// MCP config path
62 pub mcp_config_path: PathBuf,
63
64 /// Session ID (for tracking)
65 pub id: String,
66
67 /// Project context loaded from AGENTS.md, etc.
68 pub project_context: Option<ProjectContext>,
69
70 /// Repo-aware working set for context management.
71 pub working_set: WorkingSet,
72
73 /// Number of cycle boundaries crossed in this session (issue #124). The
74 /// active cycle index is `cycle_count + 1` (cycles are 1-based for users).
75 pub cycle_count: u32,
76
77 /// UTC start time of the *current* cycle. Updated when the engine resets
78 /// the conversation buffer. Used by archive headers and the `/cycles`
79 /// command's display.
80 pub current_cycle_started: DateTime<Utc>,
81
82 /// Briefings produced at past cycle boundaries, in chronological order.
83 /// Bounded growth: one entry per cycle, briefing capped at ~3,000 tokens.
84 pub cycle_briefings: Vec<CycleBriefing>,
85 }
86
87 /// Cumulative usage statistics for a session.
88 #[derive(Debug, Clone, Default)]
89 #[allow(clippy::struct_field_names)]
90 pub struct SessionUsage {
91 pub input_tokens: u64,
92 pub output_tokens: u64,
93 #[allow(dead_code)]
94 pub cache_creation_input_tokens: u64,
95 #[allow(dead_code)]
96 pub cache_read_input_tokens: u64,
97 }
98
99 impl SessionUsage {
100 /// Add usage from a turn
101 pub fn add(&mut self, usage: &Usage) {
102 self.input_tokens += u64::from(usage.input_tokens);
103 self.output_tokens += u64::from(usage.output_tokens);
104 if let Some(tokens) = usage.prompt_cache_miss_tokens {
105 self.cache_creation_input_tokens += u64::from(tokens);
106 }
107 if let Some(tokens) = usage.prompt_cache_hit_tokens {
108 self.cache_read_input_tokens += u64::from(tokens);
109 }
110 }
111 }
112
113 impl Session {
114 /// Create a new session
115 pub fn new(
116 model: String,
117 workspace: PathBuf,
118 allow_shell: bool,
119 trust_mode: bool,
120 notes_path: PathBuf,
121 mcp_config_path: PathBuf,
122 ) -> Self {
123 // Load project context from AGENTS.md, CLAUDE.md, etc.
124 let project_context = load_project_context_with_parents(&workspace);
125 let has_context = project_context.has_instructions();
126
127 Self {
128 model,
129 reasoning_effort: None,
130 reasoning_effort_auto: false,
131 auto_model: false,
132 workspace,
133 system_prompt: None,
134 compaction_summary_prompt: None,
135 messages: Vec::new(),
136 total_usage: SessionUsage::default(),
137 allow_shell,
138 trust_mode,
139 auto_approve: false,
140 approval_mode: ApprovalMode::Suggest,
141 notes_path,
142 mcp_config_path,
143 id: uuid::Uuid::new_v4().to_string(),
144 project_context: if has_context {
145 Some(project_context)
146 } else {
147 None
148 },
149 last_system_prompt_hash: None,
150 working_set: WorkingSet::default(),
151 cycle_count: 0,
152 current_cycle_started: Utc::now(),
153 cycle_briefings: Vec::new(),
154 }
155 }
156
157 /// Add a message to the conversation
158 pub fn add_message(&mut self, message: Message) {
159 self.messages.push(message);
160 }
161
162 /// Rebuild the working set from current messages (best effort).
163 pub fn rebuild_working_set(&mut self) {
164 self.working_set
165 .rebuild_from_messages(&self.messages, &self.workspace);
166 }
167 }
168
168 lines RUST