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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- 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
|