codex-usage-tracking 0.3.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.
- codex_usage_tracker/__init__.py +7 -0
- codex_usage_tracker/__main__.py +6 -0
- codex_usage_tracker/allowance.py +759 -0
- codex_usage_tracker/api_payloads.py +90 -0
- codex_usage_tracker/cli.py +1326 -0
- codex_usage_tracker/context.py +410 -0
- codex_usage_tracker/costing.py +176 -0
- codex_usage_tracker/dashboard.py +389 -0
- codex_usage_tracker/diagnostics.py +624 -0
- codex_usage_tracker/formatting.py +225 -0
- codex_usage_tracker/json_contracts.py +350 -0
- codex_usage_tracker/mcp_server.py +371 -0
- codex_usage_tracker/models.py +92 -0
- codex_usage_tracker/parser.py +491 -0
- codex_usage_tracker/paths.py +18 -0
- codex_usage_tracker/plugin_data/__init__.py +1 -0
- codex_usage_tracker/plugin_data/assets/icon.svg +8 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard.css +954 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard.js +1833 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_data.js +155 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_format.js +132 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_state.js +157 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_template.html +141 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-calls.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-details.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-insights.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-threads.png +0 -0
- codex_usage_tracker/plugin_data/docs/dashboard-guide.html +136 -0
- codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json +69 -0
- codex_usage_tracker/plugin_data/skills/codex-usage-api/SKILL.md +62 -0
- codex_usage_tracker/plugin_data/skills/codex-usage-tracker/SKILL.md +47 -0
- codex_usage_tracker/plugin_installer.py +312 -0
- codex_usage_tracker/pricing.py +57 -0
- codex_usage_tracker/pricing_config.py +223 -0
- codex_usage_tracker/pricing_estimates.py +44 -0
- codex_usage_tracker/pricing_openai.py +253 -0
- codex_usage_tracker/projects.py +347 -0
- codex_usage_tracker/recommendations.py +270 -0
- codex_usage_tracker/reports.py +637 -0
- codex_usage_tracker/schema.py +71 -0
- codex_usage_tracker/server.py +400 -0
- codex_usage_tracker/store.py +666 -0
- codex_usage_tracker/support.py +147 -0
- codex_usage_tracker/threads.py +183 -0
- codex_usage_tracking-0.3.0.dist-info/METADATA +278 -0
- codex_usage_tracking-0.3.0.dist-info/RECORD +50 -0
- codex_usage_tracking-0.3.0.dist-info/WHEEL +5 -0
- codex_usage_tracking-0.3.0.dist-info/entry_points.txt +2 -0
- codex_usage_tracking-0.3.0.dist-info/licenses/LICENSE +21 -0
- codex_usage_tracking-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""MCP server exposing aggregate-only Codex usage tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
|
|
12
|
+
from codex_usage_tracker.allowance import write_allowance_template
|
|
13
|
+
from codex_usage_tracker.api_payloads import refresh_result_payload, session_payload
|
|
14
|
+
from codex_usage_tracker.context import DEFAULT_CONTEXT_CHARS, load_call_context
|
|
15
|
+
from codex_usage_tracker.dashboard import generate_dashboard
|
|
16
|
+
from codex_usage_tracker.diagnostics import run_doctor
|
|
17
|
+
from codex_usage_tracker.formatting import (
|
|
18
|
+
format_doctor,
|
|
19
|
+
format_session,
|
|
20
|
+
)
|
|
21
|
+
from codex_usage_tracker.paths import (
|
|
22
|
+
DEFAULT_ALLOWANCE_PATH,
|
|
23
|
+
DEFAULT_CODEX_HOME,
|
|
24
|
+
DEFAULT_DASHBOARD_PATH,
|
|
25
|
+
DEFAULT_DB_PATH,
|
|
26
|
+
DEFAULT_PRICING_PATH,
|
|
27
|
+
DEFAULT_PROJECTS_PATH,
|
|
28
|
+
)
|
|
29
|
+
from codex_usage_tracker.pricing import (
|
|
30
|
+
update_pricing_from_openai_docs,
|
|
31
|
+
write_pricing_template,
|
|
32
|
+
)
|
|
33
|
+
from codex_usage_tracker.projects import apply_project_privacy_to_rows
|
|
34
|
+
from codex_usage_tracker.reports import (
|
|
35
|
+
build_expensive_calls_report,
|
|
36
|
+
build_pricing_coverage_report,
|
|
37
|
+
build_query_report,
|
|
38
|
+
build_recommendations_report,
|
|
39
|
+
build_summary_report,
|
|
40
|
+
)
|
|
41
|
+
from codex_usage_tracker.store import (
|
|
42
|
+
export_usage_csv as export_csv,
|
|
43
|
+
)
|
|
44
|
+
from codex_usage_tracker.store import (
|
|
45
|
+
query_session_usage,
|
|
46
|
+
)
|
|
47
|
+
from codex_usage_tracker.store import (
|
|
48
|
+
refresh_usage_index as refresh_index,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
mcp = FastMCP("codex-usage-tracker")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@mcp.tool()
|
|
55
|
+
def refresh_usage_index(include_archived: bool = False) -> dict[str, Any]:
|
|
56
|
+
"""Scan local Codex logs and upsert aggregate usage metrics into SQLite."""
|
|
57
|
+
|
|
58
|
+
result = refresh_index(
|
|
59
|
+
codex_home=DEFAULT_CODEX_HOME,
|
|
60
|
+
db_path=DEFAULT_DB_PATH,
|
|
61
|
+
include_archived=include_archived,
|
|
62
|
+
)
|
|
63
|
+
return refresh_result_payload(result, schema="codex-usage-tracker-refresh-v1")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@mcp.tool()
|
|
67
|
+
def usage_doctor(response_format: str = "markdown") -> str | dict[str, Any]:
|
|
68
|
+
"""Check the local plugin, MCP, database, dashboard, and pricing setup."""
|
|
69
|
+
|
|
70
|
+
report = run_doctor(db_path=DEFAULT_DB_PATH, pricing_path=DEFAULT_PRICING_PATH)
|
|
71
|
+
if response_format == "json":
|
|
72
|
+
return report
|
|
73
|
+
return format_doctor(report)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
def usage_summary(
|
|
78
|
+
group_by: str = "thread",
|
|
79
|
+
limit: int = 20,
|
|
80
|
+
preset: str | None = None,
|
|
81
|
+
since: str | None = None,
|
|
82
|
+
response_format: str = "markdown",
|
|
83
|
+
privacy_mode: str = "normal",
|
|
84
|
+
) -> str | dict[str, Any]:
|
|
85
|
+
"""Summarize aggregate Codex token usage by date, model, effort, cwd, thread, session, parent thread, or subagent metadata."""
|
|
86
|
+
|
|
87
|
+
report = build_summary_report(
|
|
88
|
+
db_path=DEFAULT_DB_PATH,
|
|
89
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
90
|
+
group_by=group_by,
|
|
91
|
+
limit=limit,
|
|
92
|
+
preset=preset,
|
|
93
|
+
since=since,
|
|
94
|
+
privacy_mode=privacy_mode,
|
|
95
|
+
)
|
|
96
|
+
if response_format == "json":
|
|
97
|
+
return report.payload()
|
|
98
|
+
return report.render()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
def session_usage(
|
|
103
|
+
session_id: str | None = None,
|
|
104
|
+
limit: int = 200,
|
|
105
|
+
response_format: str = "markdown",
|
|
106
|
+
privacy_mode: str = "normal",
|
|
107
|
+
) -> str | dict[str, Any]:
|
|
108
|
+
"""Show aggregate per-call usage for one session, defaulting to the latest indexed session."""
|
|
109
|
+
|
|
110
|
+
rows = apply_project_privacy_to_rows(
|
|
111
|
+
query_session_usage(DEFAULT_DB_PATH, session_id=session_id, limit=limit),
|
|
112
|
+
privacy_mode=privacy_mode,
|
|
113
|
+
)
|
|
114
|
+
if response_format == "json":
|
|
115
|
+
return session_payload(
|
|
116
|
+
rows,
|
|
117
|
+
requested_session_id=session_id,
|
|
118
|
+
limit=limit,
|
|
119
|
+
privacy_mode=privacy_mode,
|
|
120
|
+
)
|
|
121
|
+
return format_session(rows)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
def usage_call_context(
|
|
126
|
+
record_id: str,
|
|
127
|
+
max_chars: int = DEFAULT_CONTEXT_CHARS,
|
|
128
|
+
include_tool_output: bool = False,
|
|
129
|
+
) -> str:
|
|
130
|
+
"""Load one model call's logged local context on demand from its source JSONL file."""
|
|
131
|
+
|
|
132
|
+
if os.environ.get("CODEX_USAGE_TRACKER_ALLOW_RAW_CONTEXT") != "1":
|
|
133
|
+
return json.dumps(
|
|
134
|
+
{
|
|
135
|
+
"schema": "codex-usage-tracker-context-disabled-v1",
|
|
136
|
+
"error": (
|
|
137
|
+
"Raw context loading through MCP is disabled. Set "
|
|
138
|
+
"CODEX_USAGE_TRACKER_ALLOW_RAW_CONTEXT=1 to opt in for this process."
|
|
139
|
+
),
|
|
140
|
+
"raw_context_enabled": False,
|
|
141
|
+
"record_id": record_id,
|
|
142
|
+
},
|
|
143
|
+
indent=2,
|
|
144
|
+
)
|
|
145
|
+
payload = load_call_context(
|
|
146
|
+
record_id=record_id,
|
|
147
|
+
db_path=DEFAULT_DB_PATH,
|
|
148
|
+
max_chars=max_chars,
|
|
149
|
+
include_tool_output=include_tool_output,
|
|
150
|
+
)
|
|
151
|
+
return json.dumps(payload, indent=2)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@mcp.tool()
|
|
155
|
+
def most_expensive_usage_calls(
|
|
156
|
+
limit: int = 20,
|
|
157
|
+
preset: str | None = None,
|
|
158
|
+
since: str | None = None,
|
|
159
|
+
response_format: str = "markdown",
|
|
160
|
+
privacy_mode: str = "normal",
|
|
161
|
+
) -> str | dict[str, Any]:
|
|
162
|
+
"""Show the highest last-call aggregate usage rows with efficiency signals."""
|
|
163
|
+
|
|
164
|
+
report = build_expensive_calls_report(
|
|
165
|
+
db_path=DEFAULT_DB_PATH,
|
|
166
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
167
|
+
limit=limit,
|
|
168
|
+
preset=preset,
|
|
169
|
+
since=since,
|
|
170
|
+
privacy_mode=privacy_mode,
|
|
171
|
+
)
|
|
172
|
+
if response_format == "json":
|
|
173
|
+
return report.payload()
|
|
174
|
+
return report.render()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@mcp.tool()
|
|
178
|
+
def usage_query(
|
|
179
|
+
since: str | None = None,
|
|
180
|
+
until: str | None = None,
|
|
181
|
+
model: str | None = None,
|
|
182
|
+
effort: str | None = None,
|
|
183
|
+
thread: str | None = None,
|
|
184
|
+
project: str | None = None,
|
|
185
|
+
pricing_status: str | None = None,
|
|
186
|
+
credit_confidence: str | None = None,
|
|
187
|
+
min_tokens: int | None = None,
|
|
188
|
+
min_credits: float | None = None,
|
|
189
|
+
limit: int = 100,
|
|
190
|
+
privacy_mode: str = "normal",
|
|
191
|
+
) -> dict[str, Any]:
|
|
192
|
+
"""Return stable JSON aggregate usage rows with filters for automation."""
|
|
193
|
+
|
|
194
|
+
return build_query_report(
|
|
195
|
+
db_path=DEFAULT_DB_PATH,
|
|
196
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
197
|
+
allowance_path=DEFAULT_ALLOWANCE_PATH,
|
|
198
|
+
projects_path=DEFAULT_PROJECTS_PATH,
|
|
199
|
+
since=since,
|
|
200
|
+
until=until,
|
|
201
|
+
model=model,
|
|
202
|
+
effort=effort,
|
|
203
|
+
thread=thread,
|
|
204
|
+
project=project,
|
|
205
|
+
pricing_status=pricing_status,
|
|
206
|
+
credit_confidence=credit_confidence,
|
|
207
|
+
min_tokens=min_tokens,
|
|
208
|
+
min_credits=min_credits,
|
|
209
|
+
limit=limit,
|
|
210
|
+
privacy_mode=privacy_mode,
|
|
211
|
+
).payload
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@mcp.tool()
|
|
215
|
+
def usage_recommendations(
|
|
216
|
+
since: str | None = None,
|
|
217
|
+
until: str | None = None,
|
|
218
|
+
model: str | None = None,
|
|
219
|
+
effort: str | None = None,
|
|
220
|
+
thread: str | None = None,
|
|
221
|
+
project: str | None = None,
|
|
222
|
+
min_score: float | None = None,
|
|
223
|
+
limit: int = 20,
|
|
224
|
+
response_format: str = "markdown",
|
|
225
|
+
privacy_mode: str = "normal",
|
|
226
|
+
) -> str | dict[str, Any]:
|
|
227
|
+
"""Rank aggregate usage rows and threads by recommendation severity."""
|
|
228
|
+
|
|
229
|
+
report = build_recommendations_report(
|
|
230
|
+
db_path=DEFAULT_DB_PATH,
|
|
231
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
232
|
+
allowance_path=DEFAULT_ALLOWANCE_PATH,
|
|
233
|
+
projects_path=DEFAULT_PROJECTS_PATH,
|
|
234
|
+
since=since,
|
|
235
|
+
until=until,
|
|
236
|
+
model=model,
|
|
237
|
+
effort=effort,
|
|
238
|
+
thread=thread,
|
|
239
|
+
project=project,
|
|
240
|
+
min_score=min_score,
|
|
241
|
+
limit=limit,
|
|
242
|
+
privacy_mode=privacy_mode,
|
|
243
|
+
)
|
|
244
|
+
if response_format == "json":
|
|
245
|
+
return report.payload
|
|
246
|
+
return report.render()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@mcp.tool()
|
|
250
|
+
def usage_pricing_coverage(
|
|
251
|
+
limit: int = 20,
|
|
252
|
+
since: str | None = None,
|
|
253
|
+
response_format: str = "markdown",
|
|
254
|
+
) -> str | dict[str, Any]:
|
|
255
|
+
"""Show priced, estimated, and unpriced token coverage by model."""
|
|
256
|
+
|
|
257
|
+
report = build_pricing_coverage_report(
|
|
258
|
+
db_path=DEFAULT_DB_PATH,
|
|
259
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
260
|
+
since=since,
|
|
261
|
+
)
|
|
262
|
+
if response_format == "json":
|
|
263
|
+
return report.payload
|
|
264
|
+
return report.render(limit=limit)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@mcp.tool()
|
|
268
|
+
def generate_usage_dashboard(
|
|
269
|
+
output_path: str | None = None,
|
|
270
|
+
limit: int = 5000,
|
|
271
|
+
since: str | None = None,
|
|
272
|
+
privacy_mode: str = "normal",
|
|
273
|
+
include_archived: bool = False,
|
|
274
|
+
) -> dict[str, Any]:
|
|
275
|
+
"""Generate a local hoverable HTML dashboard from aggregate-only usage metrics."""
|
|
276
|
+
|
|
277
|
+
output = Path(output_path).expanduser() if output_path else DEFAULT_DASHBOARD_PATH
|
|
278
|
+
generated = generate_dashboard(
|
|
279
|
+
DEFAULT_DB_PATH,
|
|
280
|
+
output_path=output,
|
|
281
|
+
limit=limit,
|
|
282
|
+
pricing_path=DEFAULT_PRICING_PATH,
|
|
283
|
+
allowance_path=DEFAULT_ALLOWANCE_PATH,
|
|
284
|
+
since=since,
|
|
285
|
+
privacy_mode=privacy_mode,
|
|
286
|
+
include_archived=include_archived,
|
|
287
|
+
)
|
|
288
|
+
return {
|
|
289
|
+
"schema": "codex-usage-tracker-dashboard-v1",
|
|
290
|
+
"dashboard_path": str(generated),
|
|
291
|
+
"file_url": generated.resolve().as_uri(),
|
|
292
|
+
"opened": False,
|
|
293
|
+
"limit": None if limit <= 0 else limit,
|
|
294
|
+
"since": since,
|
|
295
|
+
"privacy_mode": privacy_mode,
|
|
296
|
+
"include_archived": include_archived,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@mcp.tool()
|
|
301
|
+
def export_usage_csv(
|
|
302
|
+
output_path: str,
|
|
303
|
+
limit: int | None = None,
|
|
304
|
+
privacy_mode: str = "normal",
|
|
305
|
+
) -> dict[str, Any]:
|
|
306
|
+
"""Export aggregate Codex token usage rows to a local CSV file."""
|
|
307
|
+
|
|
308
|
+
output = Path(output_path).expanduser()
|
|
309
|
+
rows = export_csv(
|
|
310
|
+
output_path=output,
|
|
311
|
+
db_path=DEFAULT_DB_PATH,
|
|
312
|
+
limit=limit,
|
|
313
|
+
privacy_mode=privacy_mode,
|
|
314
|
+
)
|
|
315
|
+
return {
|
|
316
|
+
"schema": "codex-usage-tracker-export-v1",
|
|
317
|
+
"rows": rows,
|
|
318
|
+
"csv_path": str(output),
|
|
319
|
+
"limit": limit,
|
|
320
|
+
"privacy_mode": privacy_mode,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@mcp.tool()
|
|
325
|
+
def init_usage_pricing_config(force: bool = False) -> dict[str, Any]:
|
|
326
|
+
"""Write a local pricing template for optional cost estimates."""
|
|
327
|
+
|
|
328
|
+
output = write_pricing_template(DEFAULT_PRICING_PATH, force=force)
|
|
329
|
+
return {
|
|
330
|
+
"schema": "codex-usage-tracker-init-pricing-v1",
|
|
331
|
+
"pricing_path": str(output),
|
|
332
|
+
"created": True,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@mcp.tool()
|
|
337
|
+
def init_usage_allowance_config(force: bool = False) -> dict[str, Any]:
|
|
338
|
+
"""Write a local template for optional Codex allowance windows."""
|
|
339
|
+
|
|
340
|
+
output = write_allowance_template(DEFAULT_ALLOWANCE_PATH, force=force)
|
|
341
|
+
return {
|
|
342
|
+
"schema": "codex-usage-tracker-init-allowance-v1",
|
|
343
|
+
"allowance_path": str(output),
|
|
344
|
+
"created": True,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@mcp.tool()
|
|
349
|
+
def update_usage_pricing_config(
|
|
350
|
+
tier: str = "standard", include_estimates: bool = True
|
|
351
|
+
) -> dict[str, Any]:
|
|
352
|
+
"""Fetch OpenAI-published text-token pricing into the local pricing config."""
|
|
353
|
+
|
|
354
|
+
result = update_pricing_from_openai_docs(
|
|
355
|
+
DEFAULT_PRICING_PATH,
|
|
356
|
+
tier=tier,
|
|
357
|
+
include_estimates=include_estimates,
|
|
358
|
+
)
|
|
359
|
+
return {
|
|
360
|
+
"schema": "codex-usage-tracker-update-pricing-v1",
|
|
361
|
+
"pricing_path": str(result.path),
|
|
362
|
+
"source_url": result.source_url,
|
|
363
|
+
"tier": result.tier,
|
|
364
|
+
"fetched_at": result.fetched_at,
|
|
365
|
+
"model_count": result.model_count,
|
|
366
|
+
"estimated_model_count": result.estimated_model_count,
|
|
367
|
+
"backup_path": str(result.backup_path) if result.backup_path else None,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if __name__ == "__main__":
|
|
371
|
+
mcp.run()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Typed records for aggregate Codex usage data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class SessionInfo:
|
|
10
|
+
"""Metadata from Codex's session index."""
|
|
11
|
+
|
|
12
|
+
session_id: str
|
|
13
|
+
thread_name: str | None
|
|
14
|
+
updated_at: str | None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class UsageEvent:
|
|
19
|
+
"""One aggregate token-count event from a Codex session log."""
|
|
20
|
+
|
|
21
|
+
record_id: str
|
|
22
|
+
session_id: str
|
|
23
|
+
thread_name: str | None
|
|
24
|
+
session_updated_at: str | None
|
|
25
|
+
event_timestamp: str
|
|
26
|
+
source_file: str
|
|
27
|
+
line_number: int
|
|
28
|
+
turn_id: str | None
|
|
29
|
+
turn_timestamp: str | None
|
|
30
|
+
cwd: str | None
|
|
31
|
+
model: str | None
|
|
32
|
+
effort: str | None
|
|
33
|
+
current_date: str | None
|
|
34
|
+
timezone: str | None
|
|
35
|
+
thread_source: str | None
|
|
36
|
+
subagent_type: str | None
|
|
37
|
+
agent_role: str | None
|
|
38
|
+
agent_nickname: str | None
|
|
39
|
+
parent_session_id: str | None
|
|
40
|
+
parent_thread_name: str | None
|
|
41
|
+
parent_session_updated_at: str | None
|
|
42
|
+
model_context_window: int | None
|
|
43
|
+
input_tokens: int
|
|
44
|
+
cached_input_tokens: int
|
|
45
|
+
output_tokens: int
|
|
46
|
+
reasoning_output_tokens: int
|
|
47
|
+
total_tokens: int
|
|
48
|
+
cumulative_input_tokens: int
|
|
49
|
+
cumulative_cached_input_tokens: int
|
|
50
|
+
cumulative_output_tokens: int
|
|
51
|
+
cumulative_reasoning_output_tokens: int
|
|
52
|
+
cumulative_total_tokens: int
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def uncached_input_tokens(self) -> int:
|
|
56
|
+
return max(self.input_tokens - self.cached_input_tokens, 0)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def cache_ratio(self) -> float:
|
|
60
|
+
if self.input_tokens <= 0:
|
|
61
|
+
return 0.0
|
|
62
|
+
return self.cached_input_tokens / self.input_tokens
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def reasoning_output_ratio(self) -> float:
|
|
66
|
+
if self.output_tokens <= 0:
|
|
67
|
+
return 0.0
|
|
68
|
+
return self.reasoning_output_tokens / self.output_tokens
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def context_window_percent(self) -> float:
|
|
72
|
+
if not self.model_context_window:
|
|
73
|
+
return 0.0
|
|
74
|
+
return self.input_tokens / self.model_context_window
|
|
75
|
+
|
|
76
|
+
def to_row(self) -> dict[str, object]:
|
|
77
|
+
row = asdict(self)
|
|
78
|
+
row["uncached_input_tokens"] = self.uncached_input_tokens
|
|
79
|
+
row["cache_ratio"] = self.cache_ratio
|
|
80
|
+
row["reasoning_output_ratio"] = self.reasoning_output_ratio
|
|
81
|
+
row["context_window_percent"] = self.context_window_percent
|
|
82
|
+
return row
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class RefreshResult:
|
|
87
|
+
scanned_files: int
|
|
88
|
+
parsed_events: int
|
|
89
|
+
inserted_or_updated_events: int
|
|
90
|
+
db_path: str
|
|
91
|
+
skipped_events: int = 0
|
|
92
|
+
parser_diagnostics: dict[str, int] = field(default_factory=dict)
|