codexapi 0.6.0__tar.gz → 0.6.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.
Files changed (23) hide show
  1. {codexapi-0.6.0/src/codexapi.egg-info → codexapi-0.6.1}/PKG-INFO +1 -1
  2. {codexapi-0.6.0 → codexapi-0.6.1}/pyproject.toml +1 -1
  3. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/__init__.py +4 -1
  4. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/cli.py +8 -0
  5. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/pushover.py +10 -0
  6. codexapi-0.6.1/src/codexapi/rate_limits.py +97 -0
  7. {codexapi-0.6.0 → codexapi-0.6.1/src/codexapi.egg-info}/PKG-INFO +1 -1
  8. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi.egg-info/SOURCES.txt +1 -0
  9. {codexapi-0.6.0 → codexapi-0.6.1}/LICENSE +0 -0
  10. {codexapi-0.6.0 → codexapi-0.6.1}/README.md +0 -0
  11. {codexapi-0.6.0 → codexapi-0.6.1}/setup.cfg +0 -0
  12. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/__main__.py +0 -0
  13. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/agent.py +0 -0
  14. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/foreach.py +0 -0
  15. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/gh_integration.py +0 -0
  16. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/ralph.py +0 -0
  17. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/science.py +0 -0
  18. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/task.py +0 -0
  19. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi/taskfile.py +0 -0
  20. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi.egg-info/dependency_links.txt +0 -0
  21. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi.egg-info/entry_points.txt +0 -0
  22. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi.egg-info/requires.txt +0 -0
  23. {codexapi-0.6.0 → codexapi-0.6.1}/src/codexapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.6.0"
7
+ version = "0.6.1"
8
8
  description = "Minimal Python API for running the Codex CLI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -3,6 +3,7 @@
3
3
  from .agent import Agent, agent
4
4
  from .foreach import ForeachResult, foreach
5
5
  from .pushover import Pushover
6
+ from .rate_limits import quota_line, rate_limits
6
7
  from .ralph import Ralph
7
8
  from .science import Science
8
9
  from .task import Task, TaskFailed, TaskResult, task, task_result
@@ -11,6 +12,8 @@ __all__ = [
11
12
  "Agent",
12
13
  "ForeachResult",
13
14
  "Pushover",
15
+ "quota_line",
16
+ "rate_limits",
14
17
  "Ralph",
15
18
  "Science",
16
19
  "Task",
@@ -21,4 +24,4 @@ __all__ = [
21
24
  "task",
22
25
  "task_result",
23
26
  ]
24
- __version__ = "0.6.0"
27
+ __version__ = "0.6.1"
@@ -18,6 +18,7 @@ from .ralph import Ralph, cancel_ralph_loop
18
18
  from .science import Science
19
19
  from .task import DEFAULT_MAX_ITERATIONS, TaskFailed, task
20
20
  from .taskfile import TaskFile, load_task_file, task_def_uses_item
21
+ from .rate_limits import quota_line
21
22
 
22
23
  _SESSION_ID_RE = re.compile(
23
24
  r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
@@ -1298,6 +1299,10 @@ def main(argv=None):
1298
1299
  "top",
1299
1300
  help="Show running Codex sessions.",
1300
1301
  )
1302
+ subparsers.add_parser(
1303
+ "limit",
1304
+ help="Show Codex rate limits.",
1305
+ )
1301
1306
 
1302
1307
  args = parser.parse_args(argv)
1303
1308
  if args.command is None:
@@ -1318,6 +1323,9 @@ def main(argv=None):
1318
1323
  if args.command == "top":
1319
1324
  _run_top([])
1320
1325
  return
1326
+ if args.command == "limit":
1327
+ print(quota_line())
1328
+ return
1321
1329
 
1322
1330
  if args.command == "foreach":
1323
1331
  if args.n is not None and args.n < 1:
@@ -8,6 +8,8 @@ import urllib.error
8
8
  import urllib.parse
9
9
  import urllib.request
10
10
 
11
+ from .rate_limits import quota_line
12
+
11
13
  _PUSHOVER_PATH = "~/.pushover"
12
14
  _PUSHOVER_URL = "https://api.pushover.net/1/messages.json"
13
15
  _MAX_MESSAGE = 1024
@@ -58,6 +60,7 @@ class Pushover:
58
60
  message_text = (message or "").strip()
59
61
  if not message_text:
60
62
  return False
63
+ message_text = _append_quota_line(message_text)
61
64
  message_text = _truncate(message_text, _MAX_MESSAGE)
62
65
  payload = urllib.parse.urlencode(
63
66
  {
@@ -177,3 +180,10 @@ def _truncate(text, limit):
177
180
 
178
181
  def _warn(message):
179
182
  print(message, file=sys.stderr)
183
+
184
+
185
+ def _append_quota_line(message):
186
+ line = quota_line()
187
+ if not line:
188
+ return message
189
+ return f"{message}\n{line}"
@@ -0,0 +1,97 @@
1
+ """Helpers for reading Codex rate limits from session logs."""
2
+
3
+ import json
4
+ import os
5
+ import time
6
+ from pathlib import Path
7
+
8
+ _QUOTA_PREFIX = "Limits:"
9
+
10
+
11
+ def rate_limits():
12
+ """Return the latest rate_limits dict from Codex session logs."""
13
+ root = Path(os.environ.get("CODEX_HOME", "~/.codex")).expanduser()
14
+ sessions = root / "sessions"
15
+ if not sessions.exists():
16
+ return None
17
+ candidates = []
18
+ for dirpath, _dirnames, filenames in os.walk(sessions):
19
+ for name in filenames:
20
+ if not name.endswith(".jsonl"):
21
+ continue
22
+ path = os.path.join(dirpath, name)
23
+ try:
24
+ mtime = os.path.getmtime(path)
25
+ except OSError:
26
+ continue
27
+ candidates.append((mtime, path))
28
+ if not candidates:
29
+ return None
30
+ for _mtime, path in sorted(candidates, reverse=True):
31
+ found = _extract_rate_limits(path)
32
+ if found is not None:
33
+ return found
34
+ return None
35
+
36
+
37
+ def _extract_rate_limits(path):
38
+ last = None
39
+ try:
40
+ with open(path, "r", encoding="utf-8", errors="replace") as handle:
41
+ for line in handle:
42
+ if '"rate_limits"' not in line:
43
+ continue
44
+ try:
45
+ event = json.loads(line)
46
+ except json.JSONDecodeError:
47
+ continue
48
+ payload = event.get("payload") or {}
49
+ rate_data = payload.get("rate_limits")
50
+ if isinstance(rate_data, dict):
51
+ last = rate_data
52
+ except OSError:
53
+ return None
54
+ return last
55
+
56
+
57
+ def quota_line():
58
+ """Return a human-readable quota line."""
59
+ data = rate_limits()
60
+ if not data:
61
+ return f"{_QUOTA_PREFIX} unavailable"
62
+ primary = data.get("primary") or {}
63
+ secondary = data.get("secondary") or {}
64
+ primary_left = _percent_left(primary.get("used_percent"))
65
+ secondary_left = _percent_left(secondary.get("used_percent"))
66
+ primary_reset = _format_reset(primary.get("resets_at"))
67
+ secondary_reset = _format_reset(secondary.get("resets_at"))
68
+ if primary_left is None or secondary_left is None:
69
+ return f"{_QUOTA_PREFIX} unavailable"
70
+ return (
71
+ f"{_QUOTA_PREFIX} {primary_left}% / {secondary_left}% left "
72
+ f"(reset in {primary_reset} / {secondary_reset})"
73
+ )
74
+
75
+
76
+ def _percent_left(used_percent):
77
+ if not isinstance(used_percent, (int, float)):
78
+ return None
79
+ left = 100.0 - float(used_percent)
80
+ if left < 0:
81
+ left = 0.0
82
+ if left > 100:
83
+ left = 100.0
84
+ return int(round(left))
85
+
86
+
87
+ def _format_reset(resets_at):
88
+ if not isinstance(resets_at, (int, float)):
89
+ return "unknown"
90
+ remaining = float(resets_at) - time.time()
91
+ if remaining < 0:
92
+ remaining = 0
93
+ hours = remaining / 3600.0
94
+ if hours > 24:
95
+ days = hours / 24.0
96
+ return f"{int(round(days))}d"
97
+ return f"{int(round(hours))}h"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -9,6 +9,7 @@ src/codexapi/foreach.py
9
9
  src/codexapi/gh_integration.py
10
10
  src/codexapi/pushover.py
11
11
  src/codexapi/ralph.py
12
+ src/codexapi/rate_limits.py
12
13
  src/codexapi/science.py
13
14
  src/codexapi/task.py
14
15
  src/codexapi/taskfile.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes