1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use crate::{HostRuntimeContext, LocalImplicitTenant, Message, ModelSpec, TenantContext};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SessionTime {
9 pub created: DateTime<Utc>,
10 pub updated: DateTime<Utc>,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Session {
15 pub id: String,
16 pub slug: Option<String>,
17 pub version: Option<String>,
18 pub project_id: Option<String>,
19 pub title: String,
20 pub directory: String,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub workspace_root: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub origin_workspace_root: Option<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub attached_from_workspace: Option<String>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub attached_to_workspace: Option<String>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub attach_timestamp_ms: Option<u64>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub attach_reason: Option<String>,
33 #[serde(default)]
34 pub tenant_context: TenantContext,
35 pub time: SessionTime,
36 pub model: Option<ModelSpec>,
37 pub provider: Option<String>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub environment: Option<HostRuntimeContext>,
40 #[serde(default)]
41 pub messages: Vec<Message>,
42}
43
44impl Session {
45 pub fn new(title: Option<String>, directory: Option<String>) -> Self {
46 let now = Utc::now();
47 Self {
48 id: Uuid::new_v4().to_string(),
49 slug: None,
50 version: Some("v1".to_string()),
51 project_id: None,
52 title: title.unwrap_or_else(|| "New session".to_string()),
53 directory: directory.unwrap_or_else(|| ".".to_string()),
54 workspace_root: None,
55 origin_workspace_root: None,
56 attached_from_workspace: None,
57 attached_to_workspace: None,
58 attach_timestamp_ms: None,
59 attach_reason: None,
60 tenant_context: LocalImplicitTenant.into(),
61 time: SessionTime {
62 created: now,
63 updated: now,
64 },
65 model: None,
66 provider: None,
67 environment: None,
68 messages: Vec::new(),
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use tandem_enterprise_contract::TenantSource;
77
78 #[test]
79 fn session_new_uses_local_implicit_tenant() {
80 let session = Session::new(Some("test".to_string()), Some(".".to_string()));
81 assert_eq!(session.tenant_context.org_id, "local");
82 assert_eq!(session.tenant_context.workspace_id, "local");
83 assert_eq!(session.tenant_context.source, TenantSource::LocalImplicit);
84 assert_eq!(session.tenant_context.actor_id, None);
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct CreateSessionRequest {
90 pub parent_id: Option<String>,
91 pub title: Option<String>,
92 pub directory: Option<String>,
93 pub workspace_root: Option<String>,
94 pub project_id: Option<String>,
95 pub model: Option<ModelSpec>,
96 pub provider: Option<String>,
97 pub permission: Option<Vec<serde_json::Value>>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct SendMessageRequest {
102 #[serde(default)]
103 pub parts: Vec<crate::MessagePartInput>,
104 pub model: Option<ModelSpec>,
105 pub agent: Option<String>,
106 #[serde(default, alias = "toolMode", alias = "tool_mode")]
107 pub tool_mode: Option<ToolMode>,
108 #[serde(default, alias = "toolAllowlist", alias = "tool_allowlist")]
109 pub tool_allowlist: Option<Vec<String>>,
110 #[serde(default, alias = "contextMode", alias = "context_mode")]
111 pub context_mode: Option<ContextMode>,
112 #[serde(default, alias = "writeRequired", alias = "write_required")]
113 pub write_required: Option<bool>,
114 #[serde(
115 default,
116 alias = "prewriteRequirements",
117 alias = "prewrite_requirements"
118 )]
119 pub prewrite_requirements: Option<PrewriteRequirements>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123pub struct PrewriteRequirements {
124 #[serde(
125 default,
126 alias = "workspaceInspectionRequired",
127 alias = "workspace_inspection_required"
128 )]
129 pub workspace_inspection_required: bool,
130 #[serde(
131 default,
132 alias = "webResearchRequired",
133 alias = "web_research_required"
134 )]
135 pub web_research_required: bool,
136 #[serde(
137 default,
138 alias = "concreteReadRequired",
139 alias = "concrete_read_required"
140 )]
141 pub concrete_read_required: bool,
142 #[serde(
143 default,
144 alias = "successfulWebResearchRequired",
145 alias = "successful_web_research_required"
146 )]
147 pub successful_web_research_required: bool,
148 #[serde(
149 default,
150 alias = "repairOnUnmetRequirements",
151 alias = "repair_on_unmet_requirements"
152 )]
153 pub repair_on_unmet_requirements: bool,
154 #[serde(default, alias = "coverageMode", alias = "coverage_mode")]
155 pub coverage_mode: PrewriteCoverageMode,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
159#[serde(rename_all = "snake_case")]
160pub enum PrewriteCoverageMode {
161 #[default]
162 None,
163 FilesReviewedBacked,
164 ResearchCorpus,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "snake_case")]
169pub enum ToolMode {
170 Auto,
171 None,
172 Required,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176#[serde(rename_all = "snake_case")]
177pub enum ContextMode {
178 Auto,
179 Compact,
180 Full,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct TodoItem {
185 pub id: String,
186 pub content: String,
187 pub status: String,
188}