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,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT — project_stats.py: pure functions computing the real, live
|
|
3
|
+
numbers the v0.7.2 roadmap's "Project & Workspace Dashboard" section
|
|
4
|
+
asks for. No Textual here — ui/dashboard.py renders what this returns.
|
|
5
|
+
|
|
6
|
+
Every function is capped/bounded and wrapped so a huge or unusual
|
|
7
|
+
workspace (a node_modules-sized folder, a symlink loop, a permission-
|
|
8
|
+
denied subdirectory) degrades to a partial/best-effort answer instead
|
|
9
|
+
of hanging the UI thread — these run synchronously on a call from
|
|
10
|
+
ui/dashboard.py's refresh, not in a worker, so a slow scan would
|
|
11
|
+
directly freeze the app.
|
|
12
|
+
|
|
13
|
+
Honest gaps, not fabricated: CPU% and a real "Workspace Health" score
|
|
14
|
+
need either `psutil` or continuous sampling this app doesn't do
|
|
15
|
+
anywhere else; rather than invent a number, cpu_percent() reports
|
|
16
|
+
`None` (not "0%") when it can't measure something real, and
|
|
17
|
+
callers show that as "unavailable" instead of a fake reading. Same
|
|
18
|
+
for "Indexed Status" — this codebase has no separate file index (the
|
|
19
|
+
Explorer's DirectoryTree reads the filesystem directly), so there's
|
|
20
|
+
nothing genuine to report; ui/dashboard.py says so rather than
|
|
21
|
+
displaying a made-up percentage.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
print("This is a library file and is not meant to be run directly.")
|
|
26
|
+
import sys
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
_IGNORE_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv",
|
|
33
|
+
".mypy_cache", ".pytest_cache", "dist", "build", ".idea", ".vscode"}
|
|
34
|
+
_LANG_BY_EXT = {
|
|
35
|
+
".py": "Python", ".js": "JavaScript", ".jsx": "JavaScript", ".ts": "TypeScript",
|
|
36
|
+
".tsx": "TypeScript", ".java": "Java", ".c": "C", ".h": "C", ".cpp": "C++",
|
|
37
|
+
".hpp": "C++", ".cs": "C#", ".go": "Go", ".rs": "Rust", ".rb": "Ruby",
|
|
38
|
+
".php": "PHP", ".swift": "Swift", ".kt": "Kotlin", ".html": "HTML",
|
|
39
|
+
".css": "CSS", ".scss": "SCSS", ".json": "JSON", ".yaml": "YAML",
|
|
40
|
+
".yml": "YAML", ".md": "Markdown", ".sh": "Shell", ".sql": "SQL",
|
|
41
|
+
".r": "R", ".lua": "Lua", ".dart": "Dart",
|
|
42
|
+
}
|
|
43
|
+
MAX_SCAN_FILES = 20000 # bail out past this so a huge workspace can't hang the UI thread
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def scan_workspace(root):
|
|
47
|
+
"""One bounded os.walk pass computing size/file-count/language
|
|
48
|
+
histogram/last-modified together, since walking the same tree three
|
|
49
|
+
separate times for three separate stats would be pure waste.
|
|
50
|
+
Returns a dict; every count is `None` if `root` isn't a real,
|
|
51
|
+
readable directory rather than a misleading 0."""
|
|
52
|
+
if not root or not os.path.isdir(root):
|
|
53
|
+
return {"total_size": None, "total_files": None, "languages": {},
|
|
54
|
+
"last_modified": None, "truncated": False}
|
|
55
|
+
total_size = 0
|
|
56
|
+
total_files = 0
|
|
57
|
+
languages = {}
|
|
58
|
+
last_modified = 0.0
|
|
59
|
+
truncated = False
|
|
60
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
61
|
+
dirnames[:] = [d for d in dirnames if d not in _IGNORE_DIRS and not d.startswith(".")]
|
|
62
|
+
for name in filenames:
|
|
63
|
+
if total_files >= MAX_SCAN_FILES:
|
|
64
|
+
truncated = True
|
|
65
|
+
break
|
|
66
|
+
path = os.path.join(dirpath, name)
|
|
67
|
+
try:
|
|
68
|
+
st = os.stat(path)
|
|
69
|
+
except OSError:
|
|
70
|
+
continue
|
|
71
|
+
total_size += st.st_size
|
|
72
|
+
total_files += 1
|
|
73
|
+
last_modified = max(last_modified, st.st_mtime)
|
|
74
|
+
ext = os.path.splitext(name)[1].lower()
|
|
75
|
+
lang = _LANG_BY_EXT.get(ext)
|
|
76
|
+
if lang:
|
|
77
|
+
languages[lang] = languages.get(lang, 0) + 1
|
|
78
|
+
if truncated:
|
|
79
|
+
break
|
|
80
|
+
return {
|
|
81
|
+
"total_size": total_size, "total_files": total_files,
|
|
82
|
+
"languages": dict(sorted(languages.items(), key=lambda kv: -kv[1])[:5]),
|
|
83
|
+
"last_modified": last_modified or None, "truncated": truncated,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def human_size(n):
|
|
88
|
+
if n is None:
|
|
89
|
+
return "\u2014"
|
|
90
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
91
|
+
if n < 1024 or unit == "TB":
|
|
92
|
+
return f"{n:.0f} {unit}" if unit == "B" else f"{n:.1f} {unit}"
|
|
93
|
+
n /= 1024
|
|
94
|
+
return f"{n:.1f} TB"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def human_age(epoch):
|
|
98
|
+
if not epoch:
|
|
99
|
+
return "\u2014"
|
|
100
|
+
delta = time.time() - epoch
|
|
101
|
+
if delta < 60:
|
|
102
|
+
return "just now"
|
|
103
|
+
if delta < 3600:
|
|
104
|
+
return f"{int(delta // 60)}m ago"
|
|
105
|
+
if delta < 86400:
|
|
106
|
+
return f"{int(delta // 3600)}h ago"
|
|
107
|
+
return f"{int(delta // 86400)}d ago"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def git_branch(root):
|
|
111
|
+
"""Reads .git/HEAD directly rather than shelling out to `git` — no
|
|
112
|
+
subprocess dependency, and it's the one file that answers "what
|
|
113
|
+
branch" without needing a full git status walk. Returns None if
|
|
114
|
+
`root` isn't a git repo at all (distinct from "detached HEAD",
|
|
115
|
+
which returns the short commit hash instead of a branch name)."""
|
|
116
|
+
head_path = os.path.join(root or "", ".git", "HEAD")
|
|
117
|
+
try:
|
|
118
|
+
with open(head_path, "r", encoding="utf-8") as f:
|
|
119
|
+
content = f.read().strip()
|
|
120
|
+
except OSError:
|
|
121
|
+
return None
|
|
122
|
+
if content.startswith("ref:"):
|
|
123
|
+
return content.split("/")[-1]
|
|
124
|
+
return content[:8] # detached HEAD: short commit hash
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
_DEP_FILES = ("requirements.txt", "package.json", "pyproject.toml", "Pipfile")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def dependencies(root):
|
|
131
|
+
"""Best-effort dependency list from whichever manifest exists —
|
|
132
|
+
real names read from the actual file, not a fabricated guess.
|
|
133
|
+
package.json is parsed as JSON; the others are read as plain lines/
|
|
134
|
+
TOML-ish text with a conservative regex rather than pulling in a
|
|
135
|
+
TOML parser dependency for one dashboard field. Capped at 12 names
|
|
136
|
+
total so a monorepo's 300-package lockfile doesn't blow out the
|
|
137
|
+
dashboard layout."""
|
|
138
|
+
if not root:
|
|
139
|
+
return []
|
|
140
|
+
names = []
|
|
141
|
+
req = os.path.join(root, "requirements.txt")
|
|
142
|
+
if os.path.isfile(req):
|
|
143
|
+
try:
|
|
144
|
+
with open(req, "r", encoding="utf-8", errors="ignore") as f:
|
|
145
|
+
for line in f:
|
|
146
|
+
line = line.strip()
|
|
147
|
+
if line and not line.startswith("#"):
|
|
148
|
+
import re
|
|
149
|
+
m = re.match(r"^([A-Za-z0-9_.\-]+)", line)
|
|
150
|
+
if m:
|
|
151
|
+
names.append(m.group(1))
|
|
152
|
+
except OSError:
|
|
153
|
+
pass
|
|
154
|
+
pkg = os.path.join(root, "package.json")
|
|
155
|
+
if os.path.isfile(pkg):
|
|
156
|
+
try:
|
|
157
|
+
import json
|
|
158
|
+
with open(pkg, "r", encoding="utf-8") as f:
|
|
159
|
+
data = json.load(f)
|
|
160
|
+
names.extend(list(data.get("dependencies", {}).keys()))
|
|
161
|
+
names.extend(list(data.get("devDependencies", {}).keys()))
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
pyproject = os.path.join(root, "pyproject.toml")
|
|
165
|
+
if os.path.isfile(pyproject):
|
|
166
|
+
try:
|
|
167
|
+
import re
|
|
168
|
+
with open(pyproject, "r", encoding="utf-8", errors="ignore") as f:
|
|
169
|
+
text = f.read()
|
|
170
|
+
for m in re.finditer(r'^\s*"?([A-Za-z0-9_.\-]+)"?\s*=', text, re.MULTILINE):
|
|
171
|
+
if m.group(1).lower() not in ("name", "version", "description",
|
|
172
|
+
"authors", "readme", "requires-python"):
|
|
173
|
+
names.append(m.group(1))
|
|
174
|
+
except OSError:
|
|
175
|
+
pass
|
|
176
|
+
seen, out = set(), []
|
|
177
|
+
for n in names:
|
|
178
|
+
if n.lower() not in seen:
|
|
179
|
+
seen.add(n.lower())
|
|
180
|
+
out.append(n)
|
|
181
|
+
if len(out) >= 12:
|
|
182
|
+
break
|
|
183
|
+
return out
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def memory_usage_mb():
|
|
187
|
+
"""Real resident-memory reading for THIS process, via stdlib
|
|
188
|
+
`resource` on POSIX (no psutil dependency needed for this one
|
|
189
|
+
number). Returns None on platforms where `resource` doesn't exist
|
|
190
|
+
(Windows) rather than faking a number — see module docstring."""
|
|
191
|
+
try:
|
|
192
|
+
import resource
|
|
193
|
+
rss_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
194
|
+
# macOS reports bytes, Linux reports KB — ru_maxrss's unit is
|
|
195
|
+
# platform-specific by design in the POSIX API itself.
|
|
196
|
+
import sys as _sys
|
|
197
|
+
if _sys.platform == "darwin":
|
|
198
|
+
return rss_kb / (1024 * 1024)
|
|
199
|
+
return rss_kb / 1024
|
|
200
|
+
except Exception:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def cpu_percent():
|
|
205
|
+
"""No portable, dependency-free way to sample process CPU% without
|
|
206
|
+
either `psutil` or maintaining our own two-sample delta over time
|
|
207
|
+
(which nothing in this app currently does) — returns None rather
|
|
208
|
+
than a fabricated number. See module docstring."""
|
|
209
|
+
return None
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT Recent/Pinned/Favorite Projects (spec v0.7 Sidebar section +
|
|
3
|
+
v0.7.7 Workspace Management Preview, sections 1/4/5).
|
|
4
|
+
|
|
5
|
+
A small, dedicated JSON store — same pattern as workspace.py and
|
|
6
|
+
config.py rather than growing either of those. Tracks folders opened
|
|
7
|
+
via the IDE's "Open Folder" (ui/sidebar.py), so they can be reopened
|
|
8
|
+
"without restarting" (spec's own phrase) and shown in the Welcome
|
|
9
|
+
Dashboard's "Recent Projects" list.
|
|
10
|
+
|
|
11
|
+
v0.7.7 additions (all backward-compatible — every pre-existing
|
|
12
|
+
function keeps its exact old behavior):
|
|
13
|
+
- per-entry labels (rename), favorites, and per-workspace sidebar
|
|
14
|
+
collapse state;
|
|
15
|
+
- `workspaces()` — one combined list of every known workspace with
|
|
16
|
+
all metadata, for the multi-workspace explorer and the history
|
|
17
|
+
panel;
|
|
18
|
+
- `search()`, `remove()`, `clear_history()`, `last_opened()`.
|
|
19
|
+
|
|
20
|
+
Deliberately NOT part of config.py's CCTConfig dataclass: that file's
|
|
21
|
+
own docstring already says new settings should land there going
|
|
22
|
+
forward, but a growing, unbounded list of recent paths is list-shaped
|
|
23
|
+
state, not a scalar setting — mixing the two would make CCTConfig's
|
|
24
|
+
schema noisy for the (many) callers that only care about the scalar
|
|
25
|
+
settings.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
print("This is a library file and is not meant to be run directly.")
|
|
30
|
+
import sys
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import time
|
|
36
|
+
|
|
37
|
+
STORE_PATH = os.path.join(os.path.expanduser("~"), ".cct_recent_projects.json")
|
|
38
|
+
# v0.7.8.1: Recent Workspaces is now the primary workspace-management
|
|
39
|
+
# surface, so the history cap grows from 12 to 20.
|
|
40
|
+
MAX_RECENT = 20
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load():
|
|
44
|
+
if not os.path.exists(STORE_PATH):
|
|
45
|
+
return {"recent": [], "pinned": []}
|
|
46
|
+
try:
|
|
47
|
+
with open(STORE_PATH, "r", encoding="utf-8") as f:
|
|
48
|
+
data = json.load(f)
|
|
49
|
+
data.setdefault("recent", [])
|
|
50
|
+
data.setdefault("pinned", [])
|
|
51
|
+
return data
|
|
52
|
+
except Exception:
|
|
53
|
+
return {"recent": [], "pinned": []}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _save(data):
|
|
57
|
+
try:
|
|
58
|
+
with open(STORE_PATH, "w", encoding="utf-8") as f:
|
|
59
|
+
json.dump(data, f, indent=2)
|
|
60
|
+
return True
|
|
61
|
+
except Exception:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _norm(path):
|
|
66
|
+
return os.path.abspath(os.path.expanduser(str(path)))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _get_entry(data, path):
|
|
70
|
+
for r in data["recent"]:
|
|
71
|
+
if r.get("path") == path:
|
|
72
|
+
return r
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def record_opened(path):
|
|
77
|
+
"""Call whenever a folder is opened as the IDE workspace root.
|
|
78
|
+
Moves `path` to the front of 'recent', de-duplicated, capped at
|
|
79
|
+
MAX_RECENT so this file doesn't grow forever. Existing metadata
|
|
80
|
+
(label/favorite/collapsed) on the entry is preserved across
|
|
81
|
+
reopens."""
|
|
82
|
+
path = _norm(path)
|
|
83
|
+
data = _load()
|
|
84
|
+
old = _get_entry(data, path)
|
|
85
|
+
meta = {}
|
|
86
|
+
if old is not None:
|
|
87
|
+
meta = {k: old[k] for k in ("label", "favorite", "collapsed") if old.get(k) is not None}
|
|
88
|
+
data["recent"] = [r for r in data["recent"] if r.get("path") != path]
|
|
89
|
+
entry = {"path": path, "opened_at": time.time()}
|
|
90
|
+
entry.update(meta)
|
|
91
|
+
data["recent"].insert(0, entry)
|
|
92
|
+
data["recent"] = data["recent"][:MAX_RECENT]
|
|
93
|
+
_save(data)
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def toggle_pin(path):
|
|
98
|
+
path = _norm(path)
|
|
99
|
+
data = _load()
|
|
100
|
+
pinned = data["pinned"]
|
|
101
|
+
if path in pinned:
|
|
102
|
+
pinned.remove(path)
|
|
103
|
+
is_pinned = False
|
|
104
|
+
else:
|
|
105
|
+
pinned.insert(0, path)
|
|
106
|
+
is_pinned = True
|
|
107
|
+
data["pinned"] = pinned
|
|
108
|
+
_save(data)
|
|
109
|
+
return is_pinned
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_pinned(path):
|
|
113
|
+
path = _norm(path)
|
|
114
|
+
return path in _load()["pinned"]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def remove_recent(path):
|
|
118
|
+
"""Removes a single entry from 'recent' (matched by absolute path) —
|
|
119
|
+
the per-project delete option in the Explorer's Recent Projects
|
|
120
|
+
list. Never touches 'pinned' (unpinning is a separate, deliberate
|
|
121
|
+
action), and never touches any file or folder on disk. Returns
|
|
122
|
+
True/False the same way _save() does; safe to call for a path
|
|
123
|
+
that isn't in the list (no-op)."""
|
|
124
|
+
path = _norm(path)
|
|
125
|
+
data = _load()
|
|
126
|
+
data["recent"] = [r for r in data["recent"] if r.get("path") != path]
|
|
127
|
+
return _save(data)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def clear_recent():
|
|
131
|
+
"""Removes every entry from 'recent' (only the stored references in
|
|
132
|
+
STORE_PATH) — never touches 'pinned', and never touches any actual
|
|
133
|
+
file or folder on disk. Safe to call even with an empty list
|
|
134
|
+
already; returns True/False the same way _save() does."""
|
|
135
|
+
data = _load()
|
|
136
|
+
data["recent"] = []
|
|
137
|
+
return _save(data)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def forget_protected():
|
|
141
|
+
"""v0.7.9.4 fix: purge any recent/pinned entry that lives inside a
|
|
142
|
+
protected system root (C:\\Windows, C:\\Program Files, ...). An
|
|
143
|
+
earlier build could record e.g. C:\\WINDOWS\\System32 as a 'recent
|
|
144
|
+
project' when `cat` was launched from PowerShell's default cwd;
|
|
145
|
+
reopening such a directory is never useful (generated-file creation
|
|
146
|
+
fails with PermissionError there). Runs once at UI startup. Never
|
|
147
|
+
raises."""
|
|
148
|
+
try:
|
|
149
|
+
from . import workspace as _ws
|
|
150
|
+
data = _load()
|
|
151
|
+
before = (len(data["recent"]), len(data["pinned"]))
|
|
152
|
+
data["recent"] = [r for r in data["recent"]
|
|
153
|
+
if not _ws.path_is_protected(r.get("path", ""))]
|
|
154
|
+
data["pinned"] = [p for p in data["pinned"]
|
|
155
|
+
if not _ws.path_is_protected(
|
|
156
|
+
p.get("path", "") if isinstance(p, dict)
|
|
157
|
+
else str(p))]
|
|
158
|
+
if (len(data["recent"]), len(data["pinned"])) != before:
|
|
159
|
+
_save(data)
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def recent():
|
|
165
|
+
return [r["path"] for r in _load()["recent"]]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def pinned():
|
|
169
|
+
return list(_load()["pinned"])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ================================================== v0.7.7 workspace mgmt ==
|
|
173
|
+
# (Workspace Management Preview, sections 4/5 — everything below is
|
|
174
|
+
# additive; nothing above changed behavior.)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def label_for(path):
|
|
178
|
+
"""The workspace's display label: a user-set custom label, else the
|
|
179
|
+
folder's base name."""
|
|
180
|
+
path = _norm(path)
|
|
181
|
+
entry = _get_entry(_load(), path)
|
|
182
|
+
if entry and entry.get("label"):
|
|
183
|
+
return entry["label"]
|
|
184
|
+
return os.path.basename(path.rstrip(os.sep)) or path
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def rename(path, label):
|
|
188
|
+
"""Sets a custom display label for a workspace. Empty/None clears
|
|
189
|
+
it back to the folder-name default. Returns the effective label."""
|
|
190
|
+
path = _norm(path)
|
|
191
|
+
label = (label or "").strip()
|
|
192
|
+
data = _load()
|
|
193
|
+
entry = _get_entry(data, path)
|
|
194
|
+
if entry is None:
|
|
195
|
+
data["recent"].insert(0, {"path": path, "opened_at": time.time(),
|
|
196
|
+
"label": label or None})
|
|
197
|
+
else:
|
|
198
|
+
if label:
|
|
199
|
+
entry["label"] = label
|
|
200
|
+
else:
|
|
201
|
+
entry.pop("label", None)
|
|
202
|
+
_save(data)
|
|
203
|
+
return label or os.path.basename(path.rstrip(os.sep)) or path
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def toggle_favorite(path):
|
|
207
|
+
"""Stars/unstars a workspace. Returns the new favorite state."""
|
|
208
|
+
path = _norm(path)
|
|
209
|
+
data = _load()
|
|
210
|
+
entry = _get_entry(data, path)
|
|
211
|
+
if entry is None:
|
|
212
|
+
data["recent"].insert(0, {"path": path, "opened_at": time.time(),
|
|
213
|
+
"favorite": True})
|
|
214
|
+
_save(data)
|
|
215
|
+
return True
|
|
216
|
+
favorite = not bool(entry.get("favorite"))
|
|
217
|
+
entry["favorite"] = favorite
|
|
218
|
+
_save(data)
|
|
219
|
+
return favorite
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def is_favorite(path):
|
|
223
|
+
path = _norm(path)
|
|
224
|
+
entry = _get_entry(_load(), path)
|
|
225
|
+
return bool(entry and entry.get("favorite"))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def favorites():
|
|
229
|
+
data = _load()
|
|
230
|
+
return [r["path"] for r in data["recent"] if r.get("favorite")]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def set_collapsed(path, collapsed):
|
|
234
|
+
"""Stores the explorer collapse state for one workspace (the
|
|
235
|
+
multi-workspace explorer collapses a workspace's tree without
|
|
236
|
+
removing it)."""
|
|
237
|
+
path = _norm(path)
|
|
238
|
+
data = _load()
|
|
239
|
+
entry = _get_entry(data, path)
|
|
240
|
+
if entry is None:
|
|
241
|
+
data["recent"].insert(0, {"path": path, "opened_at": time.time(),
|
|
242
|
+
"collapsed": bool(collapsed)})
|
|
243
|
+
else:
|
|
244
|
+
entry["collapsed"] = bool(collapsed)
|
|
245
|
+
_save(data)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def is_collapsed(path):
|
|
249
|
+
path = _norm(path)
|
|
250
|
+
entry = _get_entry(_load(), path)
|
|
251
|
+
return bool(entry and entry.get("collapsed"))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def workspaces():
|
|
255
|
+
"""One combined list of every known workspace — recent + pinned
|
|
256
|
+
union, each entry {"path", "label", "pinned", "favorite",
|
|
257
|
+
"collapsed", "opened_at"}. Pinned entries missing from recent are
|
|
258
|
+
included with their base-name label. Ordered: pinned first, then
|
|
259
|
+
most-recently-opened."""
|
|
260
|
+
data = _load()
|
|
261
|
+
by_path = {}
|
|
262
|
+
for r in data["recent"]:
|
|
263
|
+
by_path[r["path"]] = {
|
|
264
|
+
"path": r["path"],
|
|
265
|
+
"label": r.get("label") or os.path.basename(r["path"].rstrip(os.sep)) or r["path"],
|
|
266
|
+
"favorite": bool(r.get("favorite")),
|
|
267
|
+
"collapsed": bool(r.get("collapsed")),
|
|
268
|
+
"opened_at": r.get("opened_at", 0),
|
|
269
|
+
"pinned": False,
|
|
270
|
+
}
|
|
271
|
+
for p in data["pinned"]:
|
|
272
|
+
p = _norm(p)
|
|
273
|
+
if p in by_path:
|
|
274
|
+
by_path[p]["pinned"] = True
|
|
275
|
+
else:
|
|
276
|
+
by_path[p] = {
|
|
277
|
+
"path": p,
|
|
278
|
+
"label": os.path.basename(p.rstrip(os.sep)) or p,
|
|
279
|
+
"favorite": False,
|
|
280
|
+
"collapsed": False,
|
|
281
|
+
"opened_at": 0,
|
|
282
|
+
"pinned": True,
|
|
283
|
+
}
|
|
284
|
+
ordered = sorted(by_path.values(),
|
|
285
|
+
key=lambda w: (not w["pinned"], -w.get("opened_at", 0)))
|
|
286
|
+
return ordered
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def last_opened():
|
|
290
|
+
"""The most recently opened workspace path, or None."""
|
|
291
|
+
r = _load()["recent"]
|
|
292
|
+
return r[0]["path"] if r else None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def search(query):
|
|
296
|
+
"""Case-insensitive search across every known workspace's path and
|
|
297
|
+
label. Empty query returns everything (workspaces())."""
|
|
298
|
+
q = (query or "").strip().lower()
|
|
299
|
+
if not q:
|
|
300
|
+
return workspaces()
|
|
301
|
+
return [w for w in workspaces()
|
|
302
|
+
if q in w["path"].lower() or q in w["label"].lower()]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def remove(path):
|
|
306
|
+
"""Full removal from the history: recent, pinned, and favorite
|
|
307
|
+
references (pinned list holds bare paths). Never touches files on
|
|
308
|
+
disk. Returns True if anything was removed."""
|
|
309
|
+
path = _norm(path)
|
|
310
|
+
data = _load()
|
|
311
|
+
before = (len(data["recent"]), len(data["pinned"]))
|
|
312
|
+
data["recent"] = [r for r in data["recent"] if r.get("path") != path]
|
|
313
|
+
data["pinned"] = [p for p in data["pinned"] if p != path]
|
|
314
|
+
changed = (len(data["recent"]), len(data["pinned"])) != before
|
|
315
|
+
_save(data)
|
|
316
|
+
return changed
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def clear_history():
|
|
320
|
+
"""Removes every stored workspace reference (recent + pinned +
|
|
321
|
+
favorites) — only the JSON store, never files on disk. Returns
|
|
322
|
+
True when the store had anything to clear."""
|
|
323
|
+
data = _load()
|
|
324
|
+
had = bool(data["recent"] or data["pinned"])
|
|
325
|
+
data["recent"] = []
|
|
326
|
+
data["pinned"] = []
|
|
327
|
+
_save(data)
|
|
328
|
+
return had
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
calc_terminal/providers/adapters/__init__.py
|
|
3
|
+
===========================================
|
|
4
|
+
Provider Adapter Registry and Factory for CAT.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Type, Optional, Any
|
|
8
|
+
|
|
9
|
+
from .base import BaseProviderAdapter
|
|
10
|
+
from .openai_adapter import OpenAIAdapter
|
|
11
|
+
from .anthropic_adapter import AnthropicAdapter
|
|
12
|
+
from .gemini_adapter import GeminiAdapter
|
|
13
|
+
from .chinese_adapters import (
|
|
14
|
+
DeepSeekAdapter,
|
|
15
|
+
QwenAdapter,
|
|
16
|
+
MoonshotAdapter,
|
|
17
|
+
ZhipuGLMAdapter,
|
|
18
|
+
MiniMaxAdapter,
|
|
19
|
+
TencentHunyuanAdapter,
|
|
20
|
+
ByteDanceSeedAdapter,
|
|
21
|
+
XiaomiMiMoAdapter,
|
|
22
|
+
BaiduErnieAdapter,
|
|
23
|
+
)
|
|
24
|
+
from .ollama_adapter import OllamaAdapter
|
|
25
|
+
from .other_adapters import (
|
|
26
|
+
XAIAdapter,
|
|
27
|
+
MistralAdapter,
|
|
28
|
+
CohereAdapter,
|
|
29
|
+
GenericOpenAIAdapter,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_ADAPTER_REGISTRY: Dict[str, Type[BaseProviderAdapter]] = {
|
|
33
|
+
"openai": OpenAIAdapter,
|
|
34
|
+
"anthropic": AnthropicAdapter,
|
|
35
|
+
"gemini": GeminiAdapter,
|
|
36
|
+
"google": GeminiAdapter,
|
|
37
|
+
"deepseek": DeepSeekAdapter,
|
|
38
|
+
"alibaba": QwenAdapter,
|
|
39
|
+
"qwen": QwenAdapter,
|
|
40
|
+
"moonshot": MoonshotAdapter,
|
|
41
|
+
"kimi": MoonshotAdapter,
|
|
42
|
+
"zhipu": ZhipuGLMAdapter,
|
|
43
|
+
"glm": ZhipuGLMAdapter,
|
|
44
|
+
"minimax": MiniMaxAdapter,
|
|
45
|
+
"tencent": TencentHunyuanAdapter,
|
|
46
|
+
"hunyuan": TencentHunyuanAdapter,
|
|
47
|
+
"bytedance": ByteDanceSeedAdapter,
|
|
48
|
+
"seed": ByteDanceSeedAdapter,
|
|
49
|
+
"doubao": ByteDanceSeedAdapter,
|
|
50
|
+
"xiaomi": XiaomiMiMoAdapter,
|
|
51
|
+
"mimo": XiaomiMiMoAdapter,
|
|
52
|
+
"baidu": BaiduErnieAdapter,
|
|
53
|
+
"ernie": BaiduErnieAdapter,
|
|
54
|
+
"ollama": OllamaAdapter,
|
|
55
|
+
"xai": XAIAdapter,
|
|
56
|
+
"grok": XAIAdapter,
|
|
57
|
+
"mistral": MistralAdapter,
|
|
58
|
+
"cohere": CohereAdapter,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def register_adapter(provider_id: str, adapter_cls: Type[BaseProviderAdapter]):
|
|
63
|
+
"""Allow registering external or custom provider adapters."""
|
|
64
|
+
_ADAPTER_REGISTRY[provider_id.lower().strip()] = adapter_cls
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_adapter(provider_id: str, config: Optional[Dict[str, Any]] = None) -> BaseProviderAdapter:
|
|
68
|
+
"""Instantiate the appropriate adapter for the given provider."""
|
|
69
|
+
pid = (provider_id or "").lower().strip()
|
|
70
|
+
cls = _ADAPTER_REGISTRY.get(pid)
|
|
71
|
+
if cls is None:
|
|
72
|
+
adapter = GenericOpenAIAdapter(config)
|
|
73
|
+
adapter.provider_id = pid
|
|
74
|
+
adapter.display_name = pid.capitalize()
|
|
75
|
+
return adapter
|
|
76
|
+
return cls(config)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def list_supported_adapters() -> Dict[str, Type[BaseProviderAdapter]]:
|
|
80
|
+
return dict(_ADAPTER_REGISTRY)
|