superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,332 @@
1
+ """
2
+ SuperQode Usage Tracking - Token and cost tracking.
3
+
4
+ Tracks token usage and estimated cost per session.
5
+
6
+ Features:
7
+ - Per-session token tracking
8
+ - Cost estimation
9
+ - Provider-specific pricing
10
+ - History storage
11
+
12
+ Usage:
13
+ tracker = UsageTracker()
14
+ tracker.set_provider("anthropic", "claude-sonnet-4")
15
+ tracker.add_usage(1000, 500) # input, output tokens
16
+ print(tracker.get_summary())
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ from dataclasses import dataclass, field, asdict
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+ from typing import Dict, List, Optional, Tuple
26
+
27
+
28
+ # ============================================================================
29
+ # DATA CLASSES
30
+ # ============================================================================
31
+
32
+
33
+ @dataclass
34
+ class UsageEntry:
35
+ """A single usage entry."""
36
+
37
+ timestamp: str
38
+ input_tokens: int
39
+ output_tokens: int
40
+ cost: float
41
+ model: str = ""
42
+
43
+ @property
44
+ def total_tokens(self) -> int:
45
+ return self.input_tokens + self.output_tokens
46
+
47
+
48
+ @dataclass
49
+ class SessionUsage:
50
+ """Usage for a single session."""
51
+
52
+ session_id: str
53
+ provider: str
54
+ model: str
55
+ started_at: str
56
+
57
+ # Totals
58
+ total_input_tokens: int = 0
59
+ total_output_tokens: int = 0
60
+ total_cost: float = 0.0
61
+
62
+ # Messages
63
+ message_count: int = 0
64
+ tool_count: int = 0
65
+
66
+ # History
67
+ entries: List[UsageEntry] = field(default_factory=list)
68
+
69
+ @property
70
+ def total_tokens(self) -> int:
71
+ return self.total_input_tokens + self.total_output_tokens
72
+
73
+ def add_usage(
74
+ self,
75
+ input_tokens: int,
76
+ output_tokens: int,
77
+ cost: float = 0.0,
78
+ ) -> None:
79
+ """Add usage to session."""
80
+ self.total_input_tokens += input_tokens
81
+ self.total_output_tokens += output_tokens
82
+ self.total_cost += cost
83
+ self.message_count += 1
84
+
85
+ self.entries.append(
86
+ UsageEntry(
87
+ timestamp=datetime.now().isoformat(),
88
+ input_tokens=input_tokens,
89
+ output_tokens=output_tokens,
90
+ cost=cost,
91
+ model=self.model,
92
+ )
93
+ )
94
+
95
+ def add_tool_call(self) -> None:
96
+ """Record a tool call."""
97
+ self.tool_count += 1
98
+
99
+
100
+ # ============================================================================
101
+ # USAGE TRACKER
102
+ # ============================================================================
103
+
104
+
105
+ class UsageTracker:
106
+ """
107
+ Tracks token usage and cost across sessions.
108
+ """
109
+
110
+ def __init__(self):
111
+ self._current_session: Optional[SessionUsage] = None
112
+ self._history: List[SessionUsage] = []
113
+ self._config_path = Path.home() / ".superqode" / "usage.json"
114
+
115
+ # Load history
116
+ self._load_history()
117
+
118
+ def _load_history(self) -> None:
119
+ """Load usage history from file."""
120
+ try:
121
+ if self._config_path.exists():
122
+ data = json.loads(self._config_path.read_text())
123
+ # Only load summary, not full entries
124
+ self._history = []
125
+ except Exception:
126
+ pass
127
+
128
+ def _save_history(self) -> None:
129
+ """Save usage history to file."""
130
+ try:
131
+ self._config_path.parent.mkdir(parents=True, exist_ok=True)
132
+
133
+ # Save current session summary
134
+ if self._current_session:
135
+ data = {
136
+ "last_session": {
137
+ "provider": self._current_session.provider,
138
+ "model": self._current_session.model,
139
+ "total_tokens": self._current_session.total_tokens,
140
+ "total_cost": self._current_session.total_cost,
141
+ "messages": self._current_session.message_count,
142
+ },
143
+ "total_all_time": {
144
+ "tokens": sum(s.total_tokens for s in self._history)
145
+ + self._current_session.total_tokens,
146
+ "cost": sum(s.total_cost for s in self._history)
147
+ + self._current_session.total_cost,
148
+ },
149
+ }
150
+ self._config_path.write_text(json.dumps(data, indent=2))
151
+ except Exception:
152
+ pass
153
+
154
+ def start_session(
155
+ self,
156
+ provider: str,
157
+ model: str,
158
+ session_id: str = None,
159
+ ) -> None:
160
+ """Start a new usage session."""
161
+ # Save previous session
162
+ if self._current_session:
163
+ self._history.append(self._current_session)
164
+
165
+ self._current_session = SessionUsage(
166
+ session_id=session_id or datetime.now().strftime("%Y%m%d_%H%M%S"),
167
+ provider=provider,
168
+ model=model,
169
+ started_at=datetime.now().isoformat(),
170
+ )
171
+
172
+ def set_provider(self, provider: str, model: str) -> None:
173
+ """Set or update the current provider/model."""
174
+ if self._current_session:
175
+ self._current_session.provider = provider
176
+ self._current_session.model = model
177
+ else:
178
+ self.start_session(provider, model)
179
+
180
+ def add_usage(
181
+ self,
182
+ input_tokens: int,
183
+ output_tokens: int,
184
+ cost: float = None,
185
+ ) -> None:
186
+ """Add token usage."""
187
+ if not self._current_session:
188
+ self.start_session("unknown", "unknown")
189
+
190
+ # Calculate cost if not provided
191
+ if cost is None:
192
+ cost = self._estimate_cost(input_tokens, output_tokens)
193
+
194
+ self._current_session.add_usage(input_tokens, output_tokens, cost)
195
+ self._save_history()
196
+
197
+ def add_tool_call(self) -> None:
198
+ """Record a tool call."""
199
+ if self._current_session:
200
+ self._current_session.add_tool_call()
201
+
202
+ def _estimate_cost(self, input_tokens: int, output_tokens: int) -> float:
203
+ """Estimate cost based on current provider/model."""
204
+ if not self._current_session:
205
+ return 0.0
206
+
207
+ try:
208
+ from superqode.providers.models import get_model_info
209
+
210
+ info = get_model_info(self._current_session.provider, self._current_session.model)
211
+ if info:
212
+ return info.estimate_cost(input_tokens, output_tokens)
213
+ except Exception:
214
+ pass
215
+
216
+ return 0.0
217
+
218
+ def get_session_usage(self) -> Optional[SessionUsage]:
219
+ """Get current session usage."""
220
+ return self._current_session
221
+
222
+ def get_summary(self) -> Dict[str, any]:
223
+ """Get usage summary."""
224
+ if not self._current_session:
225
+ return {
226
+ "connected": False,
227
+ "provider": "",
228
+ "model": "",
229
+ "tokens": 0,
230
+ "cost": 0.0,
231
+ "messages": 0,
232
+ "tools": 0,
233
+ }
234
+
235
+ s = self._current_session
236
+ return {
237
+ "connected": True,
238
+ "provider": s.provider,
239
+ "model": s.model,
240
+ "tokens": s.total_tokens,
241
+ "input_tokens": s.total_input_tokens,
242
+ "output_tokens": s.total_output_tokens,
243
+ "cost": s.total_cost,
244
+ "messages": s.message_count,
245
+ "tools": s.tool_count,
246
+ }
247
+
248
+ def get_display_text(self) -> str:
249
+ """Get formatted display text for status bar."""
250
+ if not self._current_session:
251
+ return "Not connected"
252
+
253
+ s = self._current_session
254
+
255
+ # Format tokens
256
+ if s.total_tokens >= 1000:
257
+ tokens_str = f"{s.total_tokens / 1000:.1f}K"
258
+ else:
259
+ tokens_str = str(s.total_tokens)
260
+
261
+ # Format cost
262
+ if s.total_cost >= 0.01:
263
+ cost_str = f"${s.total_cost:.2f}"
264
+ elif s.total_cost > 0:
265
+ cost_str = f"${s.total_cost:.4f}"
266
+ else:
267
+ cost_str = "Free"
268
+
269
+ return f"{s.provider}/{s.model} | {tokens_str} tokens ({cost_str})"
270
+
271
+ def get_compact_display(self) -> Tuple[str, str, str]:
272
+ """Get compact display parts: (provider_model, tokens, cost)."""
273
+ if not self._current_session:
274
+ return ("", "", "")
275
+
276
+ s = self._current_session
277
+
278
+ # Provider/model (shortened)
279
+ provider_model = f"{s.provider[:4]}/{s.model.split('-')[0]}"
280
+
281
+ # Tokens
282
+ if s.total_tokens >= 1000:
283
+ tokens = f"{s.total_tokens // 1000}K"
284
+ else:
285
+ tokens = str(s.total_tokens)
286
+
287
+ # Cost
288
+ if s.total_cost >= 0.01:
289
+ cost = f"${s.total_cost:.2f}"
290
+ elif s.total_cost > 0:
291
+ cost = f"${s.total_cost:.3f}"
292
+ else:
293
+ cost = ""
294
+
295
+ return (provider_model, tokens, cost)
296
+
297
+ def reset(self) -> None:
298
+ """Reset current session."""
299
+ if self._current_session:
300
+ self._current_session.total_input_tokens = 0
301
+ self._current_session.total_output_tokens = 0
302
+ self._current_session.total_cost = 0.0
303
+ self._current_session.message_count = 0
304
+ self._current_session.tool_count = 0
305
+ self._current_session.entries.clear()
306
+
307
+
308
+ # ============================================================================
309
+ # GLOBAL INSTANCE
310
+ # ============================================================================
311
+
312
+ _usage_tracker: Optional[UsageTracker] = None
313
+
314
+
315
+ def get_usage_tracker() -> UsageTracker:
316
+ """Get the global usage tracker instance."""
317
+ global _usage_tracker
318
+ if _usage_tracker is None:
319
+ _usage_tracker = UsageTracker()
320
+ return _usage_tracker
321
+
322
+
323
+ # ============================================================================
324
+ # EXPORTS
325
+ # ============================================================================
326
+
327
+ __all__ = [
328
+ "UsageEntry",
329
+ "SessionUsage",
330
+ "UsageTracker",
331
+ "get_usage_tracker",
332
+ ]