claude-multiacc 2.0.22 → 2.0.23
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.
- package/README.md +2 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/codex_reset.py +6 -16
- package/lib/codex_reset_windows.py +36 -0
- package/package.json +1 -1
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/run-tests.sh +1 -1
- package/tests/test_codex_reset_windows.py +43 -0
package/README.md
CHANGED
|
@@ -378,6 +378,8 @@ Codex-specific notes:
|
|
|
378
378
|
and writes a per-account/window idempotency key before redemption so a lost response
|
|
379
379
|
or simultaneous fleet poll cannot spend a second reset. Set
|
|
380
380
|
`CODEX_MULTIACC_AUTO_RESET=0` only for emergency rollback/testing.
|
|
381
|
+
A finished-limit verdict takes precedence over a conflicting percentage. The
|
|
382
|
+
redemption key excludes unused model windows whose reset clocks move between reads.
|
|
381
383
|
- **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
|
|
382
384
|
addon's no-API-keys rule.
|
|
383
385
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/codex_reset.py
CHANGED
|
@@ -16,6 +16,8 @@ import urllib.parse
|
|
|
16
16
|
import urllib.request
|
|
17
17
|
import uuid
|
|
18
18
|
|
|
19
|
+
from codex_reset_windows import reset_windows
|
|
20
|
+
|
|
19
21
|
RESET_AT_USED_PERCENT = 95
|
|
20
22
|
REDEEM_COOLDOWN_SECONDS = 900
|
|
21
23
|
STATE_FILE = ".usage-reset.json"
|
|
@@ -100,21 +102,8 @@ def _pending_request(account_dir: str, now: int) -> tuple[dict, str]:
|
|
|
100
102
|
def _redeem_request_id(account_id: str, usage: dict, headers: dict, now: int) -> str:
|
|
101
103
|
"""Return one fleet-stable UUID for this account's current limit windows."""
|
|
102
104
|
subject = str(headers.get("chatgpt-account-id") or account_id)
|
|
103
|
-
resets =
|
|
104
|
-
|
|
105
|
-
while stack:
|
|
106
|
-
node = stack.pop()
|
|
107
|
-
if isinstance(node, dict):
|
|
108
|
-
value = node.get("reset_at")
|
|
109
|
-
if value is not None:
|
|
110
|
-
try:
|
|
111
|
-
resets.append(int(float(value)))
|
|
112
|
-
except (TypeError, ValueError):
|
|
113
|
-
pass
|
|
114
|
-
stack.extend(node.values())
|
|
115
|
-
elif isinstance(node, list):
|
|
116
|
-
stack.extend(node)
|
|
117
|
-
cycle = sorted(set(resets)) or [f"hour-{now // 3600}"]
|
|
105
|
+
_, resets = reset_windows(usage, RESET_AT_USED_PERCENT)
|
|
106
|
+
cycle = resets or [f"hour-{now // 3600}"]
|
|
118
107
|
seed = json.dumps({"account": subject, "resets": cycle}, sort_keys=True)
|
|
119
108
|
return str(uuid.uuid5(uuid.NAMESPACE_URL, seed))
|
|
120
109
|
|
|
@@ -124,7 +113,8 @@ def try_auto_redeem(account_dir: str, account_id: str, used_percent: int,
|
|
|
124
113
|
now: int | None = None) -> dict:
|
|
125
114
|
"""Redeem one available reset and return a non-secret outcome document."""
|
|
126
115
|
current = int(now if now is not None else time.time())
|
|
127
|
-
|
|
116
|
+
finished, _ = reset_windows(usage, RESET_AT_USED_PERCENT)
|
|
117
|
+
if not auto_reset_enabled() or (used_percent < RESET_AT_USED_PERCENT and not finished):
|
|
128
118
|
return {"status": "not_eligible"}
|
|
129
119
|
state, disposition = _pending_request(account_dir, current)
|
|
130
120
|
if disposition == "cooldown":
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Redemption eligibility and identity use exhausted windows, never idle model clocks."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _number(value):
|
|
5
|
+
try:
|
|
6
|
+
return float(value)
|
|
7
|
+
except (TypeError, ValueError, OverflowError):
|
|
8
|
+
return 0
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def reset_windows(usage: dict, threshold: int) -> tuple[bool, list[int]]:
|
|
12
|
+
finished, resets, stack = False, set(), [usage]
|
|
13
|
+
while stack:
|
|
14
|
+
node = stack.pop()
|
|
15
|
+
if isinstance(node, list):
|
|
16
|
+
stack.extend(node)
|
|
17
|
+
continue
|
|
18
|
+
if not isinstance(node, dict):
|
|
19
|
+
continue
|
|
20
|
+
hard_limit = node.get("limit_reached") is True or node.get("allowed") is False
|
|
21
|
+
finished = finished or hard_limit
|
|
22
|
+
windows = [node] if _number(node.get("used_percent")) >= threshold else []
|
|
23
|
+
if hard_limit:
|
|
24
|
+
reported = [node[key] for key in ("primary_window", "secondary_window")
|
|
25
|
+
if isinstance(node.get(key), dict)]
|
|
26
|
+
peak = max((_number(window.get("used_percent")) for window in reported), default=0)
|
|
27
|
+
windows.extend(window for window in reported if _number(window.get("used_percent")) == peak)
|
|
28
|
+
for window in windows:
|
|
29
|
+
try:
|
|
30
|
+
reset = int(float(window.get("reset_at")))
|
|
31
|
+
if reset > 0:
|
|
32
|
+
resets.add(reset)
|
|
33
|
+
except (TypeError, ValueError, OverflowError):
|
|
34
|
+
pass
|
|
35
|
+
stack.extend(node.values())
|
|
36
|
+
return finished, sorted(resets)
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/tests/run-tests.sh
CHANGED
|
@@ -6558,7 +6558,7 @@ unset FAKE_KEYCHAIN_DIR
|
|
|
6558
6558
|
|
|
6559
6559
|
# The reset-credit contract is easier to prove against a stateful local HTTP server
|
|
6560
6560
|
# than file:// fixtures: it pins thresholding, credit ordering and idempotent POST retry.
|
|
6561
|
-
if CODEX_MULTIACC_AUTO_RESET=1 python3 "$REPO_DIR/tests
|
|
6561
|
+
if CODEX_MULTIACC_AUTO_RESET=1 python3 -m unittest discover -s "$REPO_DIR/tests" -p 'test_codex_reset*.py'; then
|
|
6562
6562
|
t_ok "codex: automatic earned-reset integration suite"
|
|
6563
6563
|
else
|
|
6564
6564
|
t_fail "codex automatic reset suite" "see unittest output above"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Earned resets honor finished limits and ignore unrelated rolling windows."""
|
|
2
|
+
import time
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
import test_codex_reset as fixtures
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResetWindowsTest(fixtures.CodexPoolSandbox, unittest.TestCase):
|
|
9
|
+
run_limits = fixtures.CodexResetIntegrationTest.run_limits
|
|
10
|
+
|
|
11
|
+
def test_finished_limit_redeems_even_below_numeric_reset_threshold(self):
|
|
12
|
+
now = int(time.time())
|
|
13
|
+
self.server.state["usage"] = {"rate_limit": {"allowed": False, "limit_reached": True,
|
|
14
|
+
"primary_window": {"used_percent": 94, "limit_window_seconds": 604800, "reset_at": now + 86400}},
|
|
15
|
+
"rate_limit_reset_credits": {"available_count": 1}}
|
|
16
|
+
self.server.state["credits"] = {"available_count": 1, "credits": []}
|
|
17
|
+
self.server.state["responses"] = [(200, {"code": "reset", "windows_reset": 1})]
|
|
18
|
+
result = self.run_limits()
|
|
19
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
20
|
+
self.assertEqual(len(self.server.state["posts"]), 1)
|
|
21
|
+
|
|
22
|
+
def test_unused_model_windows_do_not_change_the_fleet_redemption_key(self):
|
|
23
|
+
now = int(time.time())
|
|
24
|
+
self.server.state["usage"] = {"rate_limit": {"allowed": True,
|
|
25
|
+
"primary_window": {"used_percent": 95, "limit_window_seconds": 604800, "reset_at": now + 86400}},
|
|
26
|
+
"additional_rate_limits": [{"limit_name": "Unused model", "rate_limit": {
|
|
27
|
+
"primary_window": {"used_percent": 0, "limit_window_seconds": 18000, "reset_at": now + 18000}}}],
|
|
28
|
+
"rate_limit_reset_credits": {"available_count": 2}}
|
|
29
|
+
self.server.state["credits"] = {"available_count": 2, "credits": []}
|
|
30
|
+
self.server.state["responses"] = [(200, {"code": "reset", "windows_reset": 1}),
|
|
31
|
+
(200, {"code": "already_redeemed", "windows_reset": 1})]
|
|
32
|
+
self.assertEqual(self.run_limits().returncode, 0)
|
|
33
|
+
# A fleet peer has no local receipt and sees a shifted idle-model window.
|
|
34
|
+
(self.pool / "acct-01/.usage-reset.json").unlink()
|
|
35
|
+
self.server.state["usage"]["additional_rate_limits"][0]["rate_limit"]["primary_window"]["reset_at"] += 30
|
|
36
|
+
self.assertEqual(self.run_limits().returncode, 0)
|
|
37
|
+
posts = self.server.state["posts"]
|
|
38
|
+
self.assertEqual(len(posts), 2)
|
|
39
|
+
self.assertEqual(posts[0]["redeem_request_id"], posts[1]["redeem_request_id"])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
unittest.main()
|