cct-cli 0.7.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,447 @@
1
+ """
2
+ Centralized AI Context Pipeline & Manager for CAT.
3
+
4
+ Implements Sections 1-5, 11, 15, 16, 20-23, and 35 of the
5
+ CAT AI Mode Reliability Contract.
6
+
7
+ Guarantees strict mode isolation, token budgeting, small-model context
8
+ protection, structured message schemas, and prevents cross-mode context
9
+ contamination.
10
+ """
11
+
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+ from typing import List, Dict, Any, Optional, Union
15
+ import time
16
+ import uuid
17
+
18
+ from .models.profiles import ModelProfile, get_model_profile
19
+
20
+
21
+ class AIMode(str, Enum):
22
+ CHAT = "chat"
23
+ NOTEBOOK = "notebook"
24
+ RESEARCH = "research"
25
+ PLAN = "plan"
26
+ DEBUGGER = "debugger"
27
+ BUILD = "build"
28
+ AGENT = "agent"
29
+ CODE = "code"
30
+
31
+
32
+ @dataclass
33
+ class AIMessage:
34
+ id: str
35
+ role: str # "system", "user", "assistant", "tool"
36
+ content: str
37
+ timestamp: float = field(default_factory=time.time)
38
+ request_id: str = ""
39
+ mode: str = "chat"
40
+ metadata: Dict[str, Any] = field(default_factory=dict)
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ return {
44
+ "id": self.id,
45
+ "role": self.role,
46
+ "content": self.content,
47
+ "timestamp": self.timestamp,
48
+ "request_id": self.request_id,
49
+ "mode": self.mode,
50
+ "metadata": self.metadata,
51
+ }
52
+
53
+
54
+ @dataclass
55
+ class NotebookCell:
56
+ id: str
57
+ type: str # "code" | "markdown"
58
+ source: str
59
+ outputs: List[str] = field(default_factory=list)
60
+ execution_state: str = "idle"
61
+
62
+
63
+ @dataclass
64
+ class ResearchContext:
65
+ query: str
66
+ sources: List[Dict[str, str]] = field(default_factory=list)
67
+ summaries: List[str] = field(default_factory=list)
68
+ tool_results: List[Dict[str, Any]] = field(default_factory=list)
69
+
70
+
71
+ @dataclass
72
+ class DebuggerContext:
73
+ file_path: str = ""
74
+ code_snippet: str = ""
75
+ line_number: Optional[int] = None
76
+ diagnostics: List[Dict[str, Any]] = field(default_factory=list)
77
+ stack_trace: str = ""
78
+ runtime_error: str = ""
79
+
80
+
81
+ @dataclass
82
+ class PlanContext:
83
+ task: str = ""
84
+ constraints: List[str] = field(default_factory=list)
85
+ project_state: str = ""
86
+ current_plan: str = ""
87
+ previous_plan: Optional[str] = None
88
+
89
+
90
+ @dataclass
91
+ class FileReference:
92
+ path: str
93
+ content: str
94
+ start_line: Optional[int] = None
95
+ end_line: Optional[int] = None
96
+
97
+
98
+ @dataclass
99
+ class ContextBudget:
100
+ maximum_tokens: int
101
+ reserved_for_output: int
102
+ system_tokens: int
103
+ dynamic_context_tokens: int
104
+
105
+ @classmethod
106
+ def create(cls, profile: ModelProfile, system_est: int = 150) -> "ContextBudget":
107
+ max_t = profile.max_context_tokens
108
+ reserved_out = profile.reserved_output_tokens
109
+ dyn = max(100, max_t - reserved_out - system_est)
110
+ return cls(
111
+ maximum_tokens=max_t,
112
+ reserved_for_output=reserved_out,
113
+ system_tokens=system_est,
114
+ dynamic_context_tokens=dyn,
115
+ )
116
+
117
+
118
+ @dataclass
119
+ class AIRequest:
120
+ session_id: str
121
+ request_id: str
122
+ mode: str
123
+ user_message: str
124
+ selected_files: List[FileReference] = field(default_factory=list)
125
+ selected_code: str = ""
126
+ selected_cells: List[NotebookCell] = field(default_factory=list)
127
+ diagnostics: List[Dict[str, Any]] = field(default_factory=list)
128
+ research_context: Optional[ResearchContext] = None
129
+ debugger_context: Optional[DebuggerContext] = None
130
+ plan_context: Optional[PlanContext] = None
131
+ history: List[AIMessage] = field(default_factory=list)
132
+ model: str = ""
133
+ provider: str = ""
134
+
135
+
136
+ @dataclass
137
+ class AIContext:
138
+ session_id: str
139
+ request_id: str
140
+ mode: str
141
+ system_prompt: str
142
+ messages: List[Dict[str, str]]
143
+ token_estimate: int
144
+ budget: ContextBudget
145
+ is_minimal: bool = False
146
+ metadata: Dict[str, Any] = field(default_factory=dict)
147
+
148
+
149
+ def simple_token_estimate(text: str) -> int:
150
+ """Fast character-based token estimator (~4 chars per token)."""
151
+ if not text:
152
+ return 0
153
+ return max(1, len(text) // 4)
154
+
155
+
156
+ class AIContextManager:
157
+ """Centralized context manager for all CAT AI operations."""
158
+
159
+ def __init__(self):
160
+ # session_id -> list of AIMessage
161
+ self._sessions: Dict[str, List[AIMessage]] = {}
162
+ # (session_id, mode) -> dict of isolated mode context
163
+ self._mode_contexts: Dict[tuple, Dict[str, Any]] = {}
164
+
165
+ def get_session_messages(self, session_id: str) -> List[AIMessage]:
166
+ return self._sessions.setdefault(session_id, [])
167
+
168
+ def record_message(self, session_id: str, message: AIMessage) -> None:
169
+ messages = self.get_session_messages(session_id)
170
+ messages.append(message)
171
+
172
+ def clear_conversation(self, session_id: str) -> None:
173
+ self._sessions.pop(session_id, None)
174
+ to_del = [k for k in self._mode_contexts if k[0] == session_id]
175
+ for k in to_del:
176
+ self._mode_contexts.pop(k, None)
177
+
178
+ def clear_mode_context(self, session_id: str, mode: str) -> None:
179
+ self._mode_contexts.pop((session_id, mode.lower()), None)
180
+
181
+ def estimate_tokens(self, context: Union[AIContext, str, List[Dict[str, str]]]) -> int:
182
+ if isinstance(context, str):
183
+ return simple_token_estimate(context)
184
+ if isinstance(context, AIContext):
185
+ return context.token_estimate
186
+ if isinstance(context, list):
187
+ total = 0
188
+ for m in context:
189
+ total += simple_token_estimate(m.get("content", ""))
190
+ return total
191
+ return 0
192
+
193
+ def build_context(self, request: AIRequest, profile: Optional[ModelProfile] = None) -> AIContext:
194
+ """Build an isolated, budgeted, and structured context for the AI request."""
195
+ mode = (request.mode or "chat").lower()
196
+ if profile is None:
197
+ profile = get_model_profile(request.provider, request.model)
198
+
199
+ is_small = profile.is_small_or_base()
200
+ is_minimal = profile.prompt_template == "minimal" or is_small
201
+
202
+ # 1. Mode-specific system instructions
203
+ system_instruction = self._build_system_instruction(mode, is_minimal)
204
+ sys_tokens = simple_token_estimate(system_instruction)
205
+
206
+ # 2. Token Budget calculation
207
+ budget = ContextBudget.create(profile, system_est=sys_tokens)
208
+
209
+ # 3. Filter and isolate conversation history
210
+ # CRITICAL RULE: Filter history strictly to the current mode or compatible modes.
211
+ # Do not allow massive research outputs, debugger traces, or tool dumps
212
+ # to contaminate Chat or Notebook.
213
+ filtered_history = self._filter_history_for_mode(request.history, mode, is_small)
214
+
215
+ # 4. Mode-specific context blocks (selected files, debugger, notebook, research)
216
+ mode_context_block = self._build_mode_context(request, mode, is_small)
217
+
218
+ # 5. Assemble messages with priority budgeting
219
+ messages = self._assemble_and_trim_messages(
220
+ system_instruction=system_instruction,
221
+ user_message=request.user_message,
222
+ mode_context_block=mode_context_block,
223
+ history=filtered_history,
224
+ budget=budget,
225
+ is_small=is_small,
226
+ )
227
+
228
+ total_tokens = sum(simple_token_estimate(m["content"]) for m in messages)
229
+
230
+ return AIContext(
231
+ session_id=request.session_id,
232
+ request_id=request.request_id,
233
+ mode=mode,
234
+ system_prompt=system_instruction,
235
+ messages=messages,
236
+ token_estimate=total_tokens,
237
+ budget=budget,
238
+ is_minimal=is_minimal,
239
+ metadata={
240
+ "provider": profile.provider,
241
+ "model": profile.model,
242
+ "is_small_model": is_small,
243
+ },
244
+ )
245
+
246
+ def _build_system_instruction(self, mode: str, is_minimal: bool) -> str:
247
+ """Build mode-specific system prompt without bloating small models."""
248
+ if is_minimal:
249
+ # Minimal prompt mode for small/base models
250
+ base = "You are CAT AI."
251
+ if mode == "debugger":
252
+ return f"{base} Focus on diagnosing the specific error and providing the fix."
253
+ elif mode == "plan":
254
+ return f"{base} Provide structured, step-by-step implementation plans."
255
+ elif mode == "build":
256
+ return f"{base} Write clean, working code with syntax highlighting."
257
+ elif mode == "research":
258
+ return f"{base} Analyze facts objectively and cite sources."
259
+ elif mode == "notebook":
260
+ return f"{base} Assist with scientific and analytical calculations."
261
+ return f"{base} Answer clearly, accurately, and concisely."
262
+
263
+ # Standard instructions per mode
264
+ if mode == "notebook":
265
+ return (
266
+ "You are CAT AI in Notebook Mode. Assist with computational, "
267
+ "scientific, and analytical workflows. Explain clearly with formulas and code."
268
+ )
269
+ elif mode == "research":
270
+ return (
271
+ "You are CAT AI in Research Mode. Conduct objective analysis using the "
272
+ "provided sources. Cite sources explicitly. Never present unverified source text as your own words."
273
+ )
274
+ elif mode == "plan":
275
+ return (
276
+ "You are CAT AI in Plan Mode. Create clear, pragmatic architectural roadmaps "
277
+ "and step-by-step implementation plans with explicit verification steps."
278
+ )
279
+ elif mode == "debugger":
280
+ return (
281
+ "You are CAT AI in Debugger Mode. Diagnose errors, stack traces, and bugs. "
282
+ "Provide targeted root cause analysis and the exact fix."
283
+ )
284
+ elif mode == "build":
285
+ return (
286
+ "You are CAT AI in Build Mode. Assist with software development, "
287
+ "code architecture, and implementation."
288
+ )
289
+ elif mode == "agent":
290
+ return (
291
+ "You are CAT AI in Agent Mode. Execute tasks systematically, "
292
+ "leveraging available tools and verifying each step."
293
+ )
294
+ else:
295
+ return (
296
+ "You are CAT AI, a helpful coding and problem-solving assistant. "
297
+ "Provide accurate, clear, and direct answers."
298
+ )
299
+
300
+ def _filter_history_for_mode(self, history: List[AIMessage], current_mode: str, is_small: bool) -> List[AIMessage]:
301
+ """Isolate history by mode to avoid context contamination."""
302
+ if not history:
303
+ return []
304
+
305
+ # For small models, heavily restrict history (at most 2-4 recent turns)
306
+ max_turns = 4 if is_small else 12
307
+
308
+ compatible_modes = {current_mode}
309
+ if current_mode in ("chat", "notebook"):
310
+ compatible_modes.update({"chat", "notebook"})
311
+ elif current_mode in ("code", "build"):
312
+ compatible_modes.update({"code", "build"})
313
+
314
+ filtered = []
315
+ for msg in reversed(history):
316
+ if msg.role == "system":
317
+ continue
318
+ # Must match compatible mode
319
+ msg_mode = (msg.mode or "chat").lower()
320
+ if msg_mode not in compatible_modes:
321
+ continue
322
+
323
+ # Skip massive tool outputs or research articles from normal chat context
324
+ if current_mode in ("chat", "notebook") and len(msg.content) > 4000:
325
+ continue
326
+
327
+ filtered.append(msg)
328
+ if len(filtered) >= max_turns:
329
+ break
330
+
331
+ return list(reversed(filtered))
332
+
333
+ def _build_mode_context(self, request: AIRequest, mode: str, is_small: bool) -> str:
334
+ """Construct structured, clearly demarcated context blocks for the mode."""
335
+ blocks = []
336
+
337
+ # 1. Selected Files / Code Context
338
+ if request.selected_code:
339
+ code_cap = 600 if is_small else 2000
340
+ snippet = request.selected_code[:code_cap]
341
+ blocks.append(f"--- SELECTED CODE ---\n{snippet}\n---------------------")
342
+ elif request.selected_files:
343
+ file_limit = 1 if is_small else 3
344
+ for f in request.selected_files[:file_limit]:
345
+ content_cap = 800 if is_small else 3000
346
+ content = f.content[:content_cap]
347
+ blocks.append(f"--- FILE: {f.path} ---\n{content}\n---------------------")
348
+
349
+ # 2. Notebook Context
350
+ if mode == "notebook" and request.selected_cells:
351
+ cell_limit = 2 if is_small else 6
352
+ for cell in request.selected_cells[:cell_limit]:
353
+ out = ("\nOutputs:\n" + "\n".join(cell.outputs[:2])) if cell.outputs else ""
354
+ blocks.append(f"--- CELL ({cell.type}) ---\n{cell.source[:1000]}{out}\n---------------------")
355
+
356
+ # 3. Research Context (explicitly isolated as source material)
357
+ if mode == "research" and request.research_context:
358
+ rc = request.research_context
359
+ if rc.sources:
360
+ source_lines = []
361
+ for s in rc.sources[:(2 if is_small else 5)]:
362
+ title = s.get("title", "Source")
363
+ text = s.get("text", s.get("content", ""))[: (500 if is_small else 1500)]
364
+ source_lines.append(f"[{title}]: {text}")
365
+ blocks.append("--- SOURCE DOCUMENTS (Context Only - Do not repeat verbatim) ---\n"
366
+ + "\n\n".join(source_lines) + "\n---------------------")
367
+
368
+ # 4. Debugger Context
369
+ if mode == "debugger":
370
+ dc = request.debugger_context
371
+ dbg_lines = []
372
+ if dc:
373
+ if dc.runtime_error:
374
+ dbg_lines.append(f"Runtime Error: {dc.runtime_error}")
375
+ if dc.stack_trace:
376
+ dbg_lines.append(f"Stack Trace:\n{dc.stack_trace[: (500 if is_small else 1500)]}")
377
+ if dc.code_snippet:
378
+ dbg_lines.append(f"Error Region ({dc.file_path}:{dc.line_number or '?'}):\n{dc.code_snippet[:1000]}")
379
+ if request.diagnostics:
380
+ for d in request.diagnostics[: (2 if is_small else 5)]:
381
+ dbg_lines.append(f"Diagnostic: {d}")
382
+ if dbg_lines:
383
+ blocks.append("--- DIAGNOSTIC CONTEXT ---\n" + "\n\n".join(dbg_lines) + "\n---------------------")
384
+
385
+ # 5. Plan Context
386
+ if mode == "plan" and request.plan_context:
387
+ pc = request.plan_context
388
+ plan_lines = []
389
+ if pc.task:
390
+ plan_lines.append(f"Target Task: {pc.task}")
391
+ if pc.constraints:
392
+ plan_lines.append("Constraints: " + ", ".join(pc.constraints))
393
+ if pc.previous_plan:
394
+ plan_lines.append(f"--- PREVIOUS PLAN (Reference) ---\n{pc.previous_plan[: (500 if is_small else 1500)]}")
395
+ if plan_lines:
396
+ blocks.append("\n".join(plan_lines))
397
+
398
+ return "\n\n".join(blocks).strip()
399
+
400
+ def _assemble_and_trim_messages(
401
+ self,
402
+ system_instruction: str,
403
+ user_message: str,
404
+ mode_context_block: str,
405
+ history: List[AIMessage],
406
+ budget: ContextBudget,
407
+ is_small: bool,
408
+ ) -> List[Dict[str, str]]:
409
+ """Assemble structured chat messages respecting priority budgeting."""
410
+ # Priority Order:
411
+ # 1. Current user request
412
+ # 2. System instruction
413
+ # 3. Active code / diagnostic context block
414
+ # 4. Recent relevant history turns
415
+ messages = [{"role": "system", "content": system_instruction}]
416
+
417
+ # Full final prompt combining user message and mode context
418
+ if mode_context_block:
419
+ final_user_content = f"{mode_context_block}\n\n{user_message}".strip()
420
+ else:
421
+ final_user_content = user_message.strip()
422
+
423
+ # Check remaining budget for history
424
+ used_tokens = simple_token_estimate(system_instruction) + simple_token_estimate(final_user_content)
425
+ available_for_history = max(0, budget.maximum_tokens - budget.reserved_for_output - used_tokens)
426
+
427
+ history_messages = []
428
+ for msg in reversed(history):
429
+ cost = simple_token_estimate(msg.content)
430
+ if cost <= available_for_history:
431
+ history_messages.append({"role": msg.role, "content": msg.content})
432
+ available_for_history -= cost
433
+ else:
434
+ break
435
+
436
+ history_messages.reverse()
437
+ messages.extend(history_messages)
438
+ messages.append({"role": "user", "content": final_user_content})
439
+
440
+ return messages
441
+
442
+
443
+ # Global singleton instance
444
+ _GLOBAL_CONTEXT_MANAGER = AIContextManager()
445
+
446
+ def get_context_manager() -> AIContextManager:
447
+ return _GLOBAL_CONTEXT_MANAGER