imbue-mngr-codex-usage 0.1.1__tar.gz
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.
- imbue_mngr_codex_usage-0.1.1/.gitignore +364 -0
- imbue_mngr_codex_usage-0.1.1/CHANGELOG.md +13 -0
- imbue_mngr_codex_usage-0.1.1/PKG-INFO +37 -0
- imbue_mngr_codex_usage-0.1.1/README.md +27 -0
- imbue_mngr_codex_usage-0.1.1/UNABRIDGED_CHANGELOG.md +13 -0
- imbue_mngr_codex_usage-0.1.1/changelog/.gitkeep +0 -0
- imbue_mngr_codex_usage-0.1.1/conftest.py +7 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/__init__.py +0 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/aggregate_usage_source_test.py +114 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/conftest.py +3 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/cross_boundary_test.py +37 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/plugin.py +85 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/plugin_test.py +68 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/resources/__init__.py +0 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/resources/codex_usage.sh +69 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/resources/codex_usage_emit.py +203 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/resources/codex_usage_emit_test.py +245 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/resources/codex_usage_test.py +172 -0
- imbue_mngr_codex_usage-0.1.1/imbue/mngr_codex_usage/test_ratchets.py +278 -0
- imbue_mngr_codex_usage-0.1.1/pyproject.toml +71 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
**/__pycache__/
|
|
3
|
+
**/*.py[codz]
|
|
4
|
+
**/*$py.class
|
|
5
|
+
|
|
6
|
+
# macOS Finder metadata
|
|
7
|
+
**/.DS_Store
|
|
8
|
+
|
|
9
|
+
# C extensions
|
|
10
|
+
**/*.so
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
**/.Python
|
|
14
|
+
**/build/
|
|
15
|
+
**/develop-eggs/
|
|
16
|
+
**/dist/
|
|
17
|
+
**/downloads/
|
|
18
|
+
**/eggs/
|
|
19
|
+
**/.eggs/
|
|
20
|
+
**/lib/
|
|
21
|
+
**/lib64/
|
|
22
|
+
**/parts/
|
|
23
|
+
**/sdist/
|
|
24
|
+
**/var/
|
|
25
|
+
**/wheels/
|
|
26
|
+
**/share/python-wheels/
|
|
27
|
+
**/*.egg-info/
|
|
28
|
+
**/.installed.cfg
|
|
29
|
+
**/*.egg
|
|
30
|
+
**/MANIFEST
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
**/*.manifest
|
|
36
|
+
**/*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
**/pip-log.txt
|
|
40
|
+
**/pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
**/htmlcov/
|
|
44
|
+
**/.tox/
|
|
45
|
+
**/.nox/
|
|
46
|
+
**/.coverage
|
|
47
|
+
**/.coverage.*
|
|
48
|
+
**/.cache
|
|
49
|
+
**/nosetests.xml
|
|
50
|
+
**/coverage.xml
|
|
51
|
+
**/*.cover
|
|
52
|
+
**/*.py.cover
|
|
53
|
+
**/.hypothesis/
|
|
54
|
+
**/.pytest_cache/
|
|
55
|
+
**/cover/
|
|
56
|
+
|
|
57
|
+
# Translations
|
|
58
|
+
**/*.mo
|
|
59
|
+
**/*.pot
|
|
60
|
+
|
|
61
|
+
# Django stuff:
|
|
62
|
+
**/*.log
|
|
63
|
+
**/local_settings.py
|
|
64
|
+
**/db.sqlite3
|
|
65
|
+
**/db.sqlite3-journal
|
|
66
|
+
|
|
67
|
+
# Flask stuff:
|
|
68
|
+
**/instance/
|
|
69
|
+
**/.webassets-cache
|
|
70
|
+
|
|
71
|
+
# Scrapy stuff:
|
|
72
|
+
**/.scrapy
|
|
73
|
+
|
|
74
|
+
# Sphinx documentation
|
|
75
|
+
**/docs/_build/
|
|
76
|
+
|
|
77
|
+
# PyBuilder
|
|
78
|
+
**/.pybuilder/
|
|
79
|
+
**/target/
|
|
80
|
+
|
|
81
|
+
# Jupyter Notebook
|
|
82
|
+
**/.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# IPython
|
|
85
|
+
**/profile_default/
|
|
86
|
+
**/ipython_config.py
|
|
87
|
+
|
|
88
|
+
# pyenv
|
|
89
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
90
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
91
|
+
# .python-version
|
|
92
|
+
|
|
93
|
+
# pipenv
|
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
97
|
+
# install all needed dependencies.
|
|
98
|
+
#Pipfile.lock
|
|
99
|
+
|
|
100
|
+
# UV
|
|
101
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
102
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
103
|
+
# commonly ignored for libraries.
|
|
104
|
+
#uv.lock
|
|
105
|
+
|
|
106
|
+
# poetry
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
111
|
+
#poetry.lock
|
|
112
|
+
#poetry.toml
|
|
113
|
+
|
|
114
|
+
# pdm
|
|
115
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
116
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
117
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
118
|
+
#pdm.lock
|
|
119
|
+
#pdm.toml
|
|
120
|
+
**/.pdm-python
|
|
121
|
+
**/.pdm-build/
|
|
122
|
+
|
|
123
|
+
# pixi
|
|
124
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
125
|
+
#pixi.lock
|
|
126
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
127
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
128
|
+
**/.pixi
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
**/__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
**/celerybeat-schedule
|
|
135
|
+
**/celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# SageMath parsed files
|
|
138
|
+
**/*.sage.py
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
**/.env
|
|
142
|
+
**/.envrc
|
|
143
|
+
**/.venv
|
|
144
|
+
**/.venv-*
|
|
145
|
+
**/env/
|
|
146
|
+
**/venv/
|
|
147
|
+
**/ENV/
|
|
148
|
+
**/env.bak/
|
|
149
|
+
**/venv.bak/
|
|
150
|
+
|
|
151
|
+
# Spyder project settings
|
|
152
|
+
**/.spyderproject
|
|
153
|
+
**/.spyproject
|
|
154
|
+
|
|
155
|
+
# Rope project settings
|
|
156
|
+
**/.ropeproject
|
|
157
|
+
|
|
158
|
+
# mkdocs documentation
|
|
159
|
+
**/site
|
|
160
|
+
|
|
161
|
+
# mypy
|
|
162
|
+
**/.mypy_cache/
|
|
163
|
+
**/.dmypy.json
|
|
164
|
+
**/dmypy.json
|
|
165
|
+
|
|
166
|
+
# Pyre type checker
|
|
167
|
+
**/.pyre/
|
|
168
|
+
|
|
169
|
+
# pytype static type analyzer
|
|
170
|
+
**/.pytype/
|
|
171
|
+
|
|
172
|
+
# Cython debug symbols
|
|
173
|
+
**/cython_debug/
|
|
174
|
+
|
|
175
|
+
# PyCharm
|
|
176
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
177
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
178
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
179
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
180
|
+
**/.idea/
|
|
181
|
+
|
|
182
|
+
# Abstra
|
|
183
|
+
# Abstra is an AI-powered process automation framework.
|
|
184
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
185
|
+
# Learn more at https://abstra.io/docs
|
|
186
|
+
**/.abstra/
|
|
187
|
+
|
|
188
|
+
# Visual Studio Code
|
|
189
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
190
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
192
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
193
|
+
# .vscode/
|
|
194
|
+
|
|
195
|
+
# Ruff stuff:
|
|
196
|
+
**/.ruff_cache/
|
|
197
|
+
|
|
198
|
+
# PyPI configuration file
|
|
199
|
+
**/.pypirc
|
|
200
|
+
|
|
201
|
+
# Cursor
|
|
202
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
203
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
204
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
205
|
+
**/.cursorignore
|
|
206
|
+
**/.cursorindexingignore
|
|
207
|
+
|
|
208
|
+
# Marimo
|
|
209
|
+
**/marimo/_static/
|
|
210
|
+
**/marimo/_lsp/
|
|
211
|
+
**/__marimo__/
|
|
212
|
+
|
|
213
|
+
# task folders for local custom claude workflow (at any depth, incl. the
|
|
214
|
+
# root-level `dev` project's `dev/_tasks/`, which `*/*/_tasks/` missed)
|
|
215
|
+
**/_tasks/
|
|
216
|
+
**/.sesskey
|
|
217
|
+
|
|
218
|
+
# Node.js
|
|
219
|
+
**/node_modules/
|
|
220
|
+
|
|
221
|
+
# Frontend build artifacts
|
|
222
|
+
**/frontend-dist/
|
|
223
|
+
|
|
224
|
+
# Claude Code local settings (session-specific permissions)
|
|
225
|
+
**/.claude/settings.local.json
|
|
226
|
+
|
|
227
|
+
# Local Claude settings backup files
|
|
228
|
+
**/.claude/*.bak
|
|
229
|
+
|
|
230
|
+
# Claude worktress
|
|
231
|
+
**/.claude/worktrees
|
|
232
|
+
|
|
233
|
+
# Agent definitions deployed by mngr_subagent_proxy at session start
|
|
234
|
+
**/.claude/agents/
|
|
235
|
+
|
|
236
|
+
# Local mngr settings (not committed)
|
|
237
|
+
**/.mngr/settings.local.toml
|
|
238
|
+
|
|
239
|
+
# Local working / scratch markdown (not committed)
|
|
240
|
+
**/*.local.md
|
|
241
|
+
|
|
242
|
+
# Local working / scratch shell scripts (not committed)
|
|
243
|
+
# (covers user notification scripts like scripts/notify_user.local.sh, too)
|
|
244
|
+
**/*.local.sh
|
|
245
|
+
|
|
246
|
+
# Local-only throwaway scripts (e.g. one-off test harnesses).
|
|
247
|
+
**/scripts/*.local.sh
|
|
248
|
+
|
|
249
|
+
# Local working / scratch Python + text (not committed; e.g. one-off validation
|
|
250
|
+
# harnesses or probe scripts). Mirrors the *.local.md / *.local.sh convention.
|
|
251
|
+
**/*.local.py
|
|
252
|
+
**/*.local.txt
|
|
253
|
+
|
|
254
|
+
# Test output files (slow tests, coverage summaries)
|
|
255
|
+
**/.test_output/
|
|
256
|
+
|
|
257
|
+
# TMR output directories (reports + test outputs)
|
|
258
|
+
**/tmr_*/
|
|
259
|
+
**/tmr-report/
|
|
260
|
+
|
|
261
|
+
# Active session marker file (used to detect interrupted sessions)
|
|
262
|
+
**/.claude/active
|
|
263
|
+
|
|
264
|
+
# we stick the image build artifacts here
|
|
265
|
+
**/.mngr/dev/build/
|
|
266
|
+
**/.mngr/dev/secrets/
|
|
267
|
+
|
|
268
|
+
# Offload caches and local files
|
|
269
|
+
**/.offload/**
|
|
270
|
+
**/test-results
|
|
271
|
+
|
|
272
|
+
# Minds deploy-time build artifacts.
|
|
273
|
+
# Ignore contents so we can un-ignore the committed `template/` subtree: git
|
|
274
|
+
# won't descend into a fully-ignored directory, so negations must live below
|
|
275
|
+
# the contents pattern rather than below the directory pattern.
|
|
276
|
+
**/.minds/*
|
|
277
|
+
# The template directory is committed: it defines the expected keys for each
|
|
278
|
+
# <service>.sh file, with empty values. `scripts/push_modal_secrets.py` uses
|
|
279
|
+
# it to check that per-env .sh files declare every template key.
|
|
280
|
+
!/.minds/template/
|
|
281
|
+
!/.minds/template/**
|
|
282
|
+
# Vault ACL policies for the minds tiers. The HCL is non-sensitive and is
|
|
283
|
+
# the source of truth for the policies pushed to Vault via
|
|
284
|
+
# `vault policy write <name> .minds/policies/<name>.hcl`.
|
|
285
|
+
!/.minds/policies/
|
|
286
|
+
!/.minds/policies/**
|
|
287
|
+
|
|
288
|
+
# Autofix session artifacts
|
|
289
|
+
**/.autofix/
|
|
290
|
+
**/.reviewer/logs/
|
|
291
|
+
**/.reviewer/outputs/
|
|
292
|
+
**/.reviewer/settings.local.json
|
|
293
|
+
**/.reviewer/.stop_hook_consecutive_blocks
|
|
294
|
+
**/.reviewer/conversation-issues.jsonl
|
|
295
|
+
**/.reviews/
|
|
296
|
+
|
|
297
|
+
# Stuck agent tracking (stop hook escape valve)
|
|
298
|
+
**/.claude/blocked_stop_commits
|
|
299
|
+
|
|
300
|
+
# Scheduled tasks lock file
|
|
301
|
+
**/.claude/scheduled_tasks.lock
|
|
302
|
+
|
|
303
|
+
# Demo recordings (asciinema .cast, .txt, and scripts)
|
|
304
|
+
**/.demos/
|
|
305
|
+
|
|
306
|
+
# for git worktrees from other repos
|
|
307
|
+
**/.external_worktrees/
|
|
308
|
+
|
|
309
|
+
# ignore coverage files generated within subprojects
|
|
310
|
+
**/cov.xml
|
|
311
|
+
|
|
312
|
+
# ignore demos
|
|
313
|
+
**/recordings
|
|
314
|
+
apps/minds_workspace_server/package-lock.json
|
|
315
|
+
**/*.local.md
|
|
316
|
+
|
|
317
|
+
# Generated by the _generate-dockerignore justfile recipe before each
|
|
318
|
+
# test-offload* run. The recipe strips this self-reference so offload's
|
|
319
|
+
# copy_untracked_files ships the generated file into the build context.
|
|
320
|
+
/.dockerignore
|
|
321
|
+
|
|
322
|
+
# Transient offload image-cache marker written by test-offload runs; not
|
|
323
|
+
# checked in (unlike the committed .offload-*-cache-key files which pin CI
|
|
324
|
+
# image versions).
|
|
325
|
+
/.offload-image-cache
|
|
326
|
+
|
|
327
|
+
# Generated by _generate-release-dockerfile from Dockerfile + .release.extras.
|
|
328
|
+
/libs/mngr/imbue/mngr/resources/Dockerfile.release
|
|
329
|
+
**/.reviewer/autofix/issues/
|
|
330
|
+
.reviewer/logs/
|
|
331
|
+
|
|
332
|
+
# Tailwind Play CDN JS for the minds desktop client. Fetched once via
|
|
333
|
+
# `just minds-tailwind` (plain curl, no build) and shipped with the
|
|
334
|
+
# distributable. Not checked in because it's a ~300KB blob we can always
|
|
335
|
+
# re-fetch.
|
|
336
|
+
/apps/minds/imbue/minds/desktop_client/static/tailwind.js
|
|
337
|
+
|
|
338
|
+
# macOS Finder metadata
|
|
339
|
+
**/.DS_Store
|
|
340
|
+
**/.env.swp
|
|
341
|
+
# Vim swap files for any file (`.foo.swp`, `.foo.swo`, etc.) -- generated
|
|
342
|
+
# while a file is open in vim. Never checked in.
|
|
343
|
+
**/.*.sw[a-p]
|
|
344
|
+
|
|
345
|
+
# `minds env deploy` writes a recover-target file at the monorepo root
|
|
346
|
+
# on failed deploys; `minds env recover` deletes it after rolling back.
|
|
347
|
+
# Never checked in. Per-env naming
|
|
348
|
+
# (``.minds-deploy-recover-target-<env-name>.json``) means each in-flight
|
|
349
|
+
# deploy has its own file, so concurrent deploys against different envs
|
|
350
|
+
# don't trip over each other.
|
|
351
|
+
**/.minds-deploy-*.json
|
|
352
|
+
# Per-env deploy lock files (kernel-level ``flock`` targets). Created
|
|
353
|
+
# by ``hold_deploy_lock`` in ``apps/minds/imbue/minds/envs/recover.py``
|
|
354
|
+
# and held for the entire duration of ``minds env deploy`` /
|
|
355
|
+
# ``minds env recover`` so two concurrent invocations against the same
|
|
356
|
+
# env serialize. The file itself is empty and cheap to leave around.
|
|
357
|
+
**/.minds-deploy-lock-*.lock
|
|
358
|
+
**/.antigravitycli/
|
|
359
|
+
|
|
360
|
+
# Playwright MCP scratch artifacts (mock screenshots, page snapshots)
|
|
361
|
+
**/.playwright-mcp/
|
|
362
|
+
|
|
363
|
+
# tmr (task-runner) orchestration scratch report
|
|
364
|
+
**/tmr-report/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog - mngr_codex_usage
|
|
2
|
+
|
|
3
|
+
A concise, human-friendly summary of changes for the `mngr_codex_usage` library. Entries are categorized using the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Deprecated, Removed, Fixed, Security.
|
|
4
|
+
|
|
5
|
+
For the full, unedited changelog entries, see [UNABRIDGED_CHANGELOG.md](UNABRIDGED_CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [v0.1.1] - 2026-06-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added: New package `imbue-mngr-codex-usage` providing cost/usage tracking for Codex agents in `mngr usage`. Codex reports cumulative token usage (no dollar cost), so cost is estimated from the pricing table and aggregated session-cumulatively. The writer (`codex_usage.sh`) reads codex's rollout stream and emits one `cost_snapshot` per `token_count` item; `mngr_codex`'s background-tasks supervisor launches it when present. Codex's 5h/7d rate-limit windows (subscription mode) are mapped so Codex subscription agents get Claude-style windows. The writer tracks a byte-offset cursor (persisted under `plugin/codex/.usage_cursor`) so each poll processes only the new tail of the rollout.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imbue-mngr-codex-usage
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Codex usage data provider for `mngr usage` (rollout token_count -> cost_snapshot writer + reader)
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: imbue-mngr-codex==0.1.3
|
|
7
|
+
Requires-Dist: imbue-mngr-usage==0.1.5
|
|
8
|
+
Requires-Dist: imbue-mngr==0.2.16
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# imbue-mngr-codex-usage
|
|
12
|
+
|
|
13
|
+
Codex data provider for `mngr usage`. Codex exposes no statusline or in-process
|
|
14
|
+
plugin, but mngr already tails its rollout JSONL into a raw transcript via
|
|
15
|
+
mngr_codex's background-tasks supervisor. This package adds:
|
|
16
|
+
|
|
17
|
+
- **The writer** (`codex_usage.sh`): installed into the agent's `commands/` dir,
|
|
18
|
+
where mngr_codex's `codex_background_tasks.sh` launches it **iff present**. It
|
|
19
|
+
reads the raw rollout stream and emits one `cost_snapshot` per `token_count`
|
|
20
|
+
item to `events/codex/usage/events.jsonl`.
|
|
21
|
+
- **The reader**: an `aggregate_usage_source` hookimpl claiming the `codex`
|
|
22
|
+
source, aggregated **session-cumulatively** (each `token_count` carries the
|
|
23
|
+
session's cumulative total, so the freshest per session wins).
|
|
24
|
+
|
|
25
|
+
## What gets captured
|
|
26
|
+
|
|
27
|
+
Codex reports cumulative token usage (`info.total_token_usage`) but **no dollar
|
|
28
|
+
cost**, so cost is left null and the reader **estimates** it from tokens via the
|
|
29
|
+
pricing table (provenance `ESTIMATED`). `input_tokens` includes cached, so the
|
|
30
|
+
writer emits `input = input_tokens - cached_input_tokens` and
|
|
31
|
+
`cache_read = cached_input_tokens` (OpenAI has no cache-write surcharge). The
|
|
32
|
+
model is `openai/<model>` from the rollout's `turn_context`.
|
|
33
|
+
|
|
34
|
+
In ChatGPT-plan (subscription) mode, `token_count` also carries 5h/7d rate-limit
|
|
35
|
+
windows, which the writer maps onto the window schema -- so Codex subscription
|
|
36
|
+
agents get Claude-style windows, and the session is classified `SUBSCRIPTION`
|
|
37
|
+
(imputed). Without rate limits it's `API_KEY` (real spend).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# imbue-mngr-codex-usage
|
|
2
|
+
|
|
3
|
+
Codex data provider for `mngr usage`. Codex exposes no statusline or in-process
|
|
4
|
+
plugin, but mngr already tails its rollout JSONL into a raw transcript via
|
|
5
|
+
mngr_codex's background-tasks supervisor. This package adds:
|
|
6
|
+
|
|
7
|
+
- **The writer** (`codex_usage.sh`): installed into the agent's `commands/` dir,
|
|
8
|
+
where mngr_codex's `codex_background_tasks.sh` launches it **iff present**. It
|
|
9
|
+
reads the raw rollout stream and emits one `cost_snapshot` per `token_count`
|
|
10
|
+
item to `events/codex/usage/events.jsonl`.
|
|
11
|
+
- **The reader**: an `aggregate_usage_source` hookimpl claiming the `codex`
|
|
12
|
+
source, aggregated **session-cumulatively** (each `token_count` carries the
|
|
13
|
+
session's cumulative total, so the freshest per session wins).
|
|
14
|
+
|
|
15
|
+
## What gets captured
|
|
16
|
+
|
|
17
|
+
Codex reports cumulative token usage (`info.total_token_usage`) but **no dollar
|
|
18
|
+
cost**, so cost is left null and the reader **estimates** it from tokens via the
|
|
19
|
+
pricing table (provenance `ESTIMATED`). `input_tokens` includes cached, so the
|
|
20
|
+
writer emits `input = input_tokens - cached_input_tokens` and
|
|
21
|
+
`cache_read = cached_input_tokens` (OpenAI has no cache-write surcharge). The
|
|
22
|
+
model is `openai/<model>` from the rollout's `turn_context`.
|
|
23
|
+
|
|
24
|
+
In ChatGPT-plan (subscription) mode, `token_count` also carries 5h/7d rate-limit
|
|
25
|
+
windows, which the writer maps onto the window schema -- so Codex subscription
|
|
26
|
+
agents get Claude-style windows, and the session is classified `SUBSCRIPTION`
|
|
27
|
+
(imputed). Without rate limits it's `API_KEY` (real spend).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Unabridged Changelog - mngr_codex_usage
|
|
2
|
+
|
|
3
|
+
Full, unedited changelog entries consolidated nightly from individual files in `libs/mngr_codex_usage/changelog/`.
|
|
4
|
+
|
|
5
|
+
For a concise summary, see [CHANGELOG.md](CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## 2026-06-16
|
|
8
|
+
|
|
9
|
+
New package `imbue-mngr-codex-usage`: cost/usage tracking for Codex agents in `mngr usage`. Codex reports cumulative token usage (no dollar cost), so cost is estimated from the pricing table and aggregated session-cumulatively. The writer (`codex_usage.sh`) reads codex's rollout stream and emits one `cost_snapshot` per `token_count` item; mngr_codex's background-tasks supervisor launches it when present. It also maps codex's 5h/7d rate-limit windows (subscription mode), so Codex subscription agents get Claude-style windows.
|
|
10
|
+
|
|
11
|
+
The writer tracks a byte-offset cursor (persisted under `plugin/codex/.usage_cursor`) so each poll processes only the new tail of the rollout (O(new bytes) per tick), important for long Codex sessions. Re-emission after a crash is harmless because the session-cumulative reader keeps the freshest reading per session.
|
|
12
|
+
|
|
13
|
+
The writer delegates event emission to a standalone `codex_usage_emit.py` (installed alongside `codex_usage.sh` and invoked by it), keeping that logic type-checked, linted, and unit-tested directly. Malformed rollout lines and unreadable cursor state are logged at warning level.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Unit tests for the Codex ``aggregate_usage_source`` reader hookimpl."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from imbue.mngr_codex_usage.plugin import aggregate_usage_source
|
|
10
|
+
from imbue.mngr_usage.api import parse_usage_events
|
|
11
|
+
from imbue.mngr_usage.data_types import CostMode
|
|
12
|
+
from imbue.mngr_usage.data_types import CostProvenance
|
|
13
|
+
|
|
14
|
+
_NOW = 2_800_000_000
|
|
15
|
+
_SINCE = 10**12
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _codex_event(
|
|
19
|
+
session_id: str, event_id: str, second: int, tokens: dict[str, Any], rate_limits: dict[str, Any] | None = None
|
|
20
|
+
) -> dict[str, Any]:
|
|
21
|
+
event: dict[str, Any] = {
|
|
22
|
+
"timestamp": f"2056-01-01T00:00:{second:02d}.000000000Z",
|
|
23
|
+
"session_id": session_id,
|
|
24
|
+
"event_id": event_id,
|
|
25
|
+
"cost": None,
|
|
26
|
+
"tokens": tokens,
|
|
27
|
+
"model": "openai/gpt-5.2-codex",
|
|
28
|
+
"cost_mode": "SUBSCRIPTION" if rate_limits is not None else "API_KEY",
|
|
29
|
+
}
|
|
30
|
+
if rate_limits is not None:
|
|
31
|
+
event["rate_limits"] = rate_limits
|
|
32
|
+
return event
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_codex_hookimpl_takes_freshest_cumulative_and_estimates_cost() -> None:
|
|
36
|
+
# token_count is cumulative; the freshest reading is the session total, and
|
|
37
|
+
# cost is estimated from tokens (Codex reports no dollar cost).
|
|
38
|
+
snapshot = aggregate_usage_source(
|
|
39
|
+
source_name="codex",
|
|
40
|
+
agents_events={
|
|
41
|
+
"agent-1": parse_usage_events(
|
|
42
|
+
[
|
|
43
|
+
_codex_event(
|
|
44
|
+
"s1", "line-1-usage", 1, {"input": 100, "output": 0, "cache_read": 0, "cache_creation": None}
|
|
45
|
+
),
|
|
46
|
+
_codex_event(
|
|
47
|
+
"s1",
|
|
48
|
+
"line-2-usage",
|
|
49
|
+
2,
|
|
50
|
+
{"input": 1_000_000, "output": 0, "cache_read": 0, "cache_creation": None},
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
"codex",
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
since_seconds=_SINCE,
|
|
57
|
+
now=_NOW,
|
|
58
|
+
)
|
|
59
|
+
assert snapshot is not None
|
|
60
|
+
record = snapshot.sessions[0]
|
|
61
|
+
# Freshest = 1,000,000 input * gpt-5.2-codex input price 1.75e-6 = 1.75.
|
|
62
|
+
assert record.cost.total_cost_usd == pytest.approx(1.75)
|
|
63
|
+
assert record.cost_provenance == CostProvenance.ESTIMATED
|
|
64
|
+
assert record.cost_mode == CostMode.API_KEY
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_codex_hookimpl_surfaces_windows_and_subscription_mode() -> None:
|
|
68
|
+
snapshot = aggregate_usage_source(
|
|
69
|
+
source_name="codex",
|
|
70
|
+
agents_events={
|
|
71
|
+
"agent-1": parse_usage_events(
|
|
72
|
+
[
|
|
73
|
+
_codex_event(
|
|
74
|
+
"s1",
|
|
75
|
+
"line-1-usage",
|
|
76
|
+
1,
|
|
77
|
+
{"input": 100, "output": 50, "cache_read": 200, "cache_creation": None},
|
|
78
|
+
rate_limits={
|
|
79
|
+
"five_hour": {
|
|
80
|
+
"used_percentage": 23.0,
|
|
81
|
+
"resets_at": 9_999_999_999,
|
|
82
|
+
"window_seconds": 18000,
|
|
83
|
+
},
|
|
84
|
+
"seven_day": {
|
|
85
|
+
"used_percentage": 27.0,
|
|
86
|
+
"resets_at": 9_999_999_999,
|
|
87
|
+
"window_seconds": 604800,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
],
|
|
92
|
+
"codex",
|
|
93
|
+
)
|
|
94
|
+
},
|
|
95
|
+
since_seconds=_SINCE,
|
|
96
|
+
now=_NOW,
|
|
97
|
+
)
|
|
98
|
+
assert snapshot is not None
|
|
99
|
+
assert snapshot.sessions[0].cost_mode == CostMode.SUBSCRIPTION
|
|
100
|
+
assert "five_hour" in snapshot.windows and "seven_day" in snapshot.windows
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_codex_hookimpl_declines_non_codex_sources() -> None:
|
|
104
|
+
snapshot = aggregate_usage_source(
|
|
105
|
+
source_name="opencode",
|
|
106
|
+
agents_events={
|
|
107
|
+
"agent-1": parse_usage_events(
|
|
108
|
+
[_codex_event("s1", "line-1-usage", 1, {"input": 1, "output": 1})], "opencode"
|
|
109
|
+
)
|
|
110
|
+
},
|
|
111
|
+
since_seconds=_SINCE,
|
|
112
|
+
now=_NOW,
|
|
113
|
+
)
|
|
114
|
+
assert snapshot is None
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Guard the writer-script <-> reader and supervisor seams.
|
|
2
|
+
|
|
3
|
+
The Codex usage writer (`codex_usage.sh`, this package) emits under a source the
|
|
4
|
+
reader here claims, and mngr_codex's background-tasks supervisor launches it by
|
|
5
|
+
filename. Both agreements are hand-synced string literals (across the shell <->
|
|
6
|
+
Python boundary, and across packages), so a rename silently disables usage
|
|
7
|
+
tracking. Pin both invariants.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib.resources
|
|
13
|
+
|
|
14
|
+
from imbue.mngr_codex.codex_config import BACKGROUND_TASKS_SCRIPT_NAME
|
|
15
|
+
from imbue.mngr_codex.plugin import _load_codex_resource_script
|
|
16
|
+
from imbue.mngr_codex_usage import resources as _resources
|
|
17
|
+
from imbue.mngr_codex_usage.plugin import _CODEX_USAGE_SOURCE_NAME
|
|
18
|
+
from imbue.mngr_codex_usage.plugin import _USAGE_EMIT_SCRIPT
|
|
19
|
+
from imbue.mngr_codex_usage.plugin import _USAGE_WRITER_SCRIPT
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_writer_script_emits_the_source_the_reader_claims() -> None:
|
|
23
|
+
# The emitted source literal lives in the python emitter the writer invokes.
|
|
24
|
+
emit_py = importlib.resources.files(_resources).joinpath(_USAGE_EMIT_SCRIPT).read_text()
|
|
25
|
+
assert f'"{_CODEX_USAGE_SOURCE_NAME}/usage"' in emit_py
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_writer_script_invokes_the_emitter_it_ships_with() -> None:
|
|
29
|
+
writer_sh = importlib.resources.files(_resources).joinpath(_USAGE_WRITER_SCRIPT).read_text()
|
|
30
|
+
assert _USAGE_EMIT_SCRIPT in writer_sh
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_supervisor_launches_the_writer_by_the_name_this_package_installs() -> None:
|
|
34
|
+
supervisor_sh = _load_codex_resource_script(BACKGROUND_TASKS_SCRIPT_NAME)
|
|
35
|
+
# mngr_codex's supervisor must reference the exact script filename this
|
|
36
|
+
# package installs into commands/, or the writer never launches.
|
|
37
|
+
assert _USAGE_WRITER_SCRIPT in supervisor_sh
|