1pub mod client;
10pub mod error;
11pub mod server;
12
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub struct ThoughtData {
20 pub thought: String,
22 pub thought_number: u32,
24 pub total_thoughts: u32,
26 pub next_thought_needed: bool,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub is_revision: Option<bool>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub revises_thought: Option<u32>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub branch_from_thought: Option<u32>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub branch_id: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub needs_more_thoughts: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub timestamp: Option<chrono::DateTime<chrono::Utc>>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub metadata: Option<HashMap<String, serde_json::Value>>,
49}
50
51impl Default for ThoughtData {
52 fn default() -> Self {
53 Self {
54 thought: String::new(),
55 thought_number: 1,
56 total_thoughts: 1,
57 next_thought_needed: true,
58 is_revision: None,
59 revises_thought: None,
60 branch_from_thought: None,
61 branch_id: None,
62 needs_more_thoughts: None,
63 timestamp: Some(chrono::Utc::now()),
64 metadata: None,
65 }
66 }
67}
68
69impl ThoughtData {
70 pub fn new(thought: String, thought_number: u32, total_thoughts: u32) -> Self {
72 Self {
73 thought,
74 thought_number,
75 total_thoughts,
76 next_thought_needed: true,
77 is_revision: None,
78 revises_thought: None,
79 branch_from_thought: None,
80 branch_id: None,
81 needs_more_thoughts: None,
82 timestamp: Some(chrono::Utc::now()),
83 metadata: None,
84 }
85 }
86
87 pub fn revision(thought: String, thought_number: u32, revises_thought: u32) -> Self {
89 Self {
90 thought,
91 thought_number,
92 total_thoughts: thought_number,
93 next_thought_needed: true,
94 is_revision: Some(true),
95 revises_thought: Some(revises_thought),
96 branch_from_thought: None,
97 branch_id: None,
98 needs_more_thoughts: None,
99 timestamp: Some(chrono::Utc::now()),
100 metadata: None,
101 }
102 }
103
104 pub fn branch(
106 thought: String,
107 thought_number: u32,
108 branch_from_thought: u32,
109 branch_id: String,
110 ) -> Self {
111 Self {
112 thought,
113 thought_number,
114 total_thoughts: thought_number,
115 next_thought_needed: true,
116 is_revision: None,
117 revises_thought: None,
118 branch_from_thought: Some(branch_from_thought),
119 branch_id: Some(branch_id),
120 needs_more_thoughts: None,
121 timestamp: Some(chrono::Utc::now()),
122 metadata: None,
123 }
124 }
125
126 pub fn is_revision(&self) -> bool {
128 self.is_revision.unwrap_or(false)
129 }
130
131 pub fn is_branch(&self) -> bool {
133 self.branch_from_thought.is_some()
134 }
135
136 pub fn get_branch_id(&self) -> Option<&str> {
138 self.branch_id.as_deref()
139 }
140
141 pub fn get_revised_thought(&self) -> Option<u32> {
143 self.revises_thought
144 }
145
146 pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
148 if self.metadata.is_none() {
149 self.metadata = Some(HashMap::new());
150 }
151 if let Some(ref mut metadata) = self.metadata {
152 metadata.insert(key, value);
153 }
154 self
155 }
156
157 pub fn validate(&self) -> Result<(), String> {
159 if self.thought.is_empty() {
160 return Err("Thought content cannot be empty".to_string());
161 }
162 if self.thought_number == 0 {
163 return Err("Thought number must be greater than 0".to_string());
164 }
165 if self.total_thoughts == 0 {
166 return Err("Total thoughts must be greater than 0".to_string());
167 }
168 if self.thought_number > self.total_thoughts {
169 }
171 if self.is_revision() && self.revises_thought.is_none() {
172 return Err(
173 "Revision thoughts must specify which thought is being revised".to_string(),
174 );
175 }
176 if self.is_branch() && self.branch_id.is_none() {
177 return Err("Branch thoughts must have a branch ID".to_string());
178 }
179 Ok(())
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ThoughtBranch {
186 pub branch_id: String,
188 pub parent_thought: u32,
190 pub thoughts: Vec<ThoughtData>,
192 pub metadata: HashMap<String, serde_json::Value>,
194 pub created_at: chrono::DateTime<chrono::Utc>,
196}
197
198impl ThoughtBranch {
199 pub fn new(branch_id: String, parent_thought: u32) -> Self {
201 Self {
202 branch_id,
203 parent_thought,
204 thoughts: Vec::new(),
205 metadata: HashMap::new(),
206 created_at: chrono::Utc::now(),
207 }
208 }
209
210 pub fn add_thought(&mut self, thought: ThoughtData) {
212 self.thoughts.push(thought);
213 }
214
215 pub fn thought_count(&self) -> usize {
217 self.thoughts.len()
218 }
219
220 pub fn latest_thought(&self) -> Option<&ThoughtData> {
222 self.thoughts.last()
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct ThinkingProgress {
229 pub current_thought: u32,
231 pub total_thoughts: u32,
233 pub completed_thoughts: u32,
235 pub active_branches: usize,
237 pub needs_more_thoughts: bool,
239 pub progress_percentage: f64,
241 pub estimated_time_remaining: Option<std::time::Duration>,
243}
244
245impl ThinkingProgress {
246 pub fn new(current_thought: u32, total_thoughts: u32) -> Self {
248 let completed_thoughts = current_thought.saturating_sub(1);
249 let progress_percentage = if total_thoughts > 0 {
250 completed_thoughts as f64 / total_thoughts as f64
251 } else {
252 0.0
253 };
254
255 Self {
256 current_thought,
257 total_thoughts,
258 completed_thoughts,
259 active_branches: 0,
260 needs_more_thoughts: true,
261 progress_percentage,
262 estimated_time_remaining: None,
263 }
264 }
265
266 pub fn update(&mut self, thought: &ThoughtData) {
268 self.current_thought = thought.thought_number;
269 self.total_thoughts = thought.total_thoughts;
270 self.completed_thoughts = thought.thought_number.saturating_sub(1);
271 self.needs_more_thoughts = thought.next_thought_needed;
272
273 self.progress_percentage = if self.total_thoughts > 0 {
274 self.completed_thoughts as f64 / self.total_thoughts as f64
275 } else {
276 0.0
277 };
278 }
279
280 pub fn is_complete(&self) -> bool {
282 !self.needs_more_thoughts && self.completed_thoughts >= self.total_thoughts
283 }
284}
285
286#[async_trait::async_trait]
288pub trait ThoughtProcessor: Send + Sync {
289 async fn process_thought(&self, thought: ThoughtData) -> Result<ThoughtData, String>;
291
292 async fn validate_thought(&self, thought: &ThoughtData) -> Result<(), String>;
294
295 async fn get_stats(&self) -> Result<ThinkingStats, String>;
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct ThinkingStats {
302 pub total_thoughts: u64,
304 pub total_revisions: u64,
306 pub total_branches: u64,
308 pub avg_processing_time_ms: f64,
310 pub total_processing_time_ms: u64,
312 pub total_thought_length: u64,
314}
315
316impl Default for ThinkingStats {
317 fn default() -> Self {
318 Self {
319 total_thoughts: 0,
320 total_revisions: 0,
321 total_branches: 0,
322 avg_processing_time_ms: 0.0,
323 total_processing_time_ms: 0,
324 total_thought_length: 0,
325 }
326 }
327}
328
329#[derive(Debug)]
331pub struct ThinkingEngine {
332 id: Uuid,
334 session_id: Option<String>,
336 thoughts: Vec<ThoughtData>,
338 branches: HashMap<String, ThoughtBranch>,
340 progress: ThinkingProgress,
342 stats: ThinkingStats,
344 disable_logging: bool,
346}
347
348impl ThinkingEngine {
349 pub fn new() -> Self {
351 Self {
352 id: Uuid::new_v4(),
353 session_id: None,
354 thoughts: Vec::new(),
355 branches: HashMap::new(),
356 progress: ThinkingProgress::new(1, 1),
357 stats: ThinkingStats::default(),
358 disable_logging: false,
359 }
360 }
361
362 pub fn with_logging(disable_logging: bool) -> Self {
364 Self {
365 disable_logging,
366 ..Self::new()
367 }
368 }
369
370 pub fn start_session(&mut self, session_id: String) {
372 self.session_id = Some(session_id);
373 self.thoughts.clear();
374 self.branches.clear();
375 self.progress = ThinkingProgress::new(1, 1);
376 self.stats = ThinkingStats::default();
377 }
378
379 pub async fn process_thought(&mut self, thought: ThoughtData) -> Result<ThoughtData, String> {
381 let start_time = std::time::Instant::now();
382
383 thought.validate()?;
385
386 let mut processed_thought = thought.clone();
388 if processed_thought.thought_number > processed_thought.total_thoughts {
389 processed_thought.total_thoughts = processed_thought.thought_number;
390 }
391
392 self.thoughts.push(processed_thought.clone());
394
395 if let (Some(branch_from), Some(branch_id)) = (
397 processed_thought.branch_from_thought,
398 &processed_thought.branch_id,
399 ) {
400 let branch = self
401 .branches
402 .entry(branch_id.clone())
403 .or_insert_with(|| ThoughtBranch::new(branch_id.clone(), branch_from));
404 branch.add_thought(processed_thought.clone());
405 }
406
407 self.progress.update(&processed_thought);
409
410 let processing_time = start_time.elapsed();
412 self.stats.total_thoughts += 1;
413 self.stats.total_processing_time_ms += processing_time.as_millis() as u64;
414 self.stats.avg_processing_time_ms =
415 self.stats.total_processing_time_ms as f64 / self.stats.total_thoughts as f64;
416
417 if processed_thought.is_revision() {
418 self.stats.total_revisions += 1;
419 }
420 if processed_thought.is_branch() {
421 self.stats.total_branches += 1;
422 }
423
424 if !self.disable_logging {
426 self.log_thought(&processed_thought);
427 }
428
429 Ok(processed_thought)
430 }
431
432 pub fn get_progress(&self) -> &ThinkingProgress {
434 &self.progress
435 }
436
437 pub fn get_thoughts(&self) -> &[ThoughtData] {
439 &self.thoughts
440 }
441
442 pub fn get_branches(&self) -> &HashMap<String, ThoughtBranch> {
444 &self.branches
445 }
446
447 pub fn get_stats(&self) -> &ThinkingStats {
449 &self.stats
450 }
451
452 pub fn is_complete(&self) -> bool {
454 self.progress.is_complete()
455 }
456
457 pub fn session_id(&self) -> Option<&str> {
459 self.session_id.as_deref()
460 }
461
462 pub fn engine_id(&self) -> Uuid {
464 self.id
465 }
466
467 fn log_thought(&self, thought: &ThoughtData) {
469 let prefix = if thought.is_revision() {
470 "š Revision"
471 } else if thought.is_branch() {
472 "šæ Branch"
473 } else {
474 "š Thought"
475 };
476
477 let context = if thought.is_revision() {
478 format!(
479 " (revising thought {})",
480 thought.revises_thought.unwrap_or(0)
481 )
482 } else if thought.is_branch() {
483 format!(
484 " (from thought {}, ID: {})",
485 thought.branch_from_thought.unwrap_or(0),
486 thought.branch_id.as_deref().unwrap_or("unknown")
487 )
488 } else {
489 String::new()
490 };
491
492 let header = format!(
493 "{} {}/{}",
494 prefix, thought.thought_number, thought.total_thoughts
495 );
496 let border_length = std::cmp::max(header.len() + context.len(), thought.thought.len()) + 4;
497 let border = "ā".repeat(border_length);
498
499 eprintln!(
500 "\nā{}ā\nā {}{} ā\nā{}ā¤\nā {} ā\nā{}ā",
501 border, header, context, border, thought.thought, border
502 );
503 }
504}
505
506impl Default for ThinkingEngine {
507 fn default() -> Self {
508 Self::new()
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn test_thought_data_creation() {
518 let thought = ThoughtData::new("Test thought".to_string(), 1, 5);
519 assert_eq!(thought.thought, "Test thought");
520 assert_eq!(thought.thought_number, 1);
521 assert_eq!(thought.total_thoughts, 5);
522 assert!(thought.next_thought_needed);
523 assert!(!thought.is_revision());
524 assert!(!thought.is_branch());
525 }
526
527 #[test]
528 fn test_revision_thought() {
529 let thought = ThoughtData::revision("Revised thought".to_string(), 3, 1);
530 assert!(thought.is_revision());
531 assert_eq!(thought.get_revised_thought(), Some(1));
532 assert_eq!(thought.revises_thought, Some(1));
533 }
534
535 #[test]
536 fn test_branch_thought() {
537 let thought =
538 ThoughtData::branch("Branch thought".to_string(), 4, 2, "branch-1".to_string());
539 assert!(thought.is_branch());
540 assert_eq!(thought.get_branch_id(), Some("branch-1"));
541 assert_eq!(thought.branch_from_thought, Some(2));
542 }
543
544 #[test]
545 fn test_thought_validation() {
546 let valid_thought = ThoughtData::new("Valid thought".to_string(), 1, 5);
547 assert!(valid_thought.validate().is_ok());
548
549 let invalid_thought = ThoughtData {
550 thought: String::new(),
551 thought_number: 1,
552 total_thoughts: 5,
553 next_thought_needed: true,
554 ..Default::default()
555 };
556 assert!(invalid_thought.validate().is_err());
557 }
558
559 #[tokio::test]
560 async fn test_thinking_engine() {
561 let mut engine = ThinkingEngine::new();
562 engine.start_session("test-session".to_string());
563
564 let thought = ThoughtData::new("First thought".to_string(), 1, 3);
565 let processed = engine.process_thought(thought).await.unwrap();
566
567 assert_eq!(processed.thought, "First thought");
568 assert_eq!(engine.get_thoughts().len(), 1);
569 assert!(!engine.is_complete());
570 }
571
572 #[test]
573 fn test_thinking_progress() {
574 let mut progress = ThinkingProgress::new(1, 5);
575 assert_eq!(progress.current_thought, 1);
576 assert_eq!(progress.total_thoughts, 5);
577 assert_eq!(progress.progress_percentage, 0.0);
578
579 let thought = ThoughtData::new("Test".to_string(), 3, 5);
580 progress.update(&thought);
581 assert_eq!(progress.current_thought, 3);
582 assert_eq!(progress.completed_thoughts, 2);
583 assert_eq!(progress.progress_percentage, 0.4);
584 }
585}