@cocorograph/hub-agent 0.7.84 → 0.7.85
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/package.json +1 -1
- package/src/usage.mjs +117 -17
package/package.json
CHANGED
package/src/usage.mjs
CHANGED
|
@@ -1483,6 +1483,117 @@ export function _resetCodexTokenCountMemo() {
|
|
|
1483
1483
|
_codexTokenCountMemo.clear()
|
|
1484
1484
|
}
|
|
1485
1485
|
|
|
1486
|
+
/**
|
|
1487
|
+
* Codex rate limit の窓分類定数 (T06407)。5h 窓 = 300 分、7d 窓 = 10080 分。
|
|
1488
|
+
* ⚠️ 位置 (primary/secondary) は 5h/7d を保証しない。実測 (0.144.x / pro プラン) では
|
|
1489
|
+
* primary が windowDurationMins=10080 (=7日)・secondary=null を返す。よって窓の分類は
|
|
1490
|
+
* **duration で行う** (症状⑦「7d が進まない」の根治)。閾値は 5h と 7d の中点 (~5190 分)。
|
|
1491
|
+
*/
|
|
1492
|
+
const CODEX_5H_WINDOW_MINS = 300
|
|
1493
|
+
const CODEX_7D_WINDOW_MINS = 10080
|
|
1494
|
+
/** 分類スロットの正準窓長。窓は「最も近い正準窓」のスロットへ割り当てる(中点固定分割だと
|
|
1495
|
+
* 将来 1h+5h のような同帯 2 窓で実 5h を取りこぼす。codex-review minor 対応)。 */
|
|
1496
|
+
const CODEX_RATE_LIMIT_SLOTS = [
|
|
1497
|
+
{ key: "last5h", canonicalMins: CODEX_5H_WINDOW_MINS },
|
|
1498
|
+
{ key: "last7d", canonicalMins: CODEX_7D_WINDOW_MINS },
|
|
1499
|
+
]
|
|
1500
|
+
|
|
1501
|
+
/** RateLimitWindow (App Server = camelCase / rollout jsonl = snake_case の両対応) から
|
|
1502
|
+
* used% を読む。値が無ければ null (= その窓は「未提供」)。 */
|
|
1503
|
+
function codexWindowUsedPercent(w) {
|
|
1504
|
+
if (!w) return null
|
|
1505
|
+
if (typeof w.usedPercent === "number") return w.usedPercent
|
|
1506
|
+
if (typeof w.used_percent === "number") return w.used_percent
|
|
1507
|
+
return null
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/** RateLimitWindow の窓長 (分)。camelCase/snake_case 両対応。不明なら null。 */
|
|
1511
|
+
function codexWindowMins(w) {
|
|
1512
|
+
if (!w) return null
|
|
1513
|
+
if (typeof w.windowDurationMins === "number") return w.windowDurationMins
|
|
1514
|
+
if (typeof w.window_minutes === "number") return w.window_minutes
|
|
1515
|
+
return null
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/** RateLimitWindow の resets 時刻 (ms)。camelCase/snake_case 両対応。秒→ms 補正あり。 */
|
|
1519
|
+
function codexWindowResetMs(w) {
|
|
1520
|
+
if (!w) return null
|
|
1521
|
+
const raw =
|
|
1522
|
+
typeof w.resetsAt === "number"
|
|
1523
|
+
? w.resetsAt
|
|
1524
|
+
: typeof w.resets_at === "number"
|
|
1525
|
+
? w.resets_at
|
|
1526
|
+
: null
|
|
1527
|
+
if (typeof raw !== "number") return null
|
|
1528
|
+
return raw < 1e12 ? raw * 1000 : raw
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/** 「未提供」bucket。欠落窓を 0% と誤表示せず、frontend が「未提供」と描画できるよう
|
|
1532
|
+
* provided:false / percent:null を返す (fail-soft で沈黙させない)。 */
|
|
1533
|
+
function codexNotProvidedBucket() {
|
|
1534
|
+
return {
|
|
1535
|
+
tokens: 0,
|
|
1536
|
+
messages: 0,
|
|
1537
|
+
limit: CODEX_DEFAULT_LIMIT,
|
|
1538
|
+
percent: null,
|
|
1539
|
+
provided: false,
|
|
1540
|
+
windowMinutes: null,
|
|
1541
|
+
oldestTsMs: null,
|
|
1542
|
+
resetAtMs: null,
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* RateLimitSnapshot (App Server の `account/rateLimits/read`/`updated` または rollout の
|
|
1548
|
+
* `rate_limits`、両 casing) を **窓長で 5h/7d スロットへ分類**する (T06407)。
|
|
1549
|
+
*
|
|
1550
|
+
* - primary/secondary の**位置に依存しない**。各窓の windowDurationMins/window_minutes を見て、
|
|
1551
|
+
* **正準窓長に最も近いスロット**へ割り当てる (5h=300 分 / 7d=10080 分)。
|
|
1552
|
+
* - 提供されない窓 (null / used% 欠落 / duration 不明) は 0% ではなく「未提供」で返す。
|
|
1553
|
+
* - 同一スロットに複数窓が来たら、そのスロットの正準窓長に**より近い**窓を採用する
|
|
1554
|
+
* (先着でなく最近傍。将来 1h+5h のような同帯 2 窓でも実 5h を取りこぼさない)。
|
|
1555
|
+
*
|
|
1556
|
+
* @param {object|null} snapshot RateLimitSnapshot 相当 ({ primary, secondary, ... })
|
|
1557
|
+
* @returns {{ last5h: object, last7d: object }} 各 bucket は provided:true|false を持つ
|
|
1558
|
+
*/
|
|
1559
|
+
export function classifyCodexRateLimitWindows(snapshot) {
|
|
1560
|
+
const result = { last5h: codexNotProvidedBucket(), last7d: codexNotProvidedBucket() }
|
|
1561
|
+
if (!snapshot) return result
|
|
1562
|
+
// 各スロットに「これまで割り当てた窓の正準窓長からの距離」を持ち、より近い窓で上書きする。
|
|
1563
|
+
const bestDist = { last5h: Infinity, last7d: Infinity }
|
|
1564
|
+
for (const w of [snapshot.primary, snapshot.secondary]) {
|
|
1565
|
+
const usedPercent = codexWindowUsedPercent(w)
|
|
1566
|
+
if (usedPercent === null) continue
|
|
1567
|
+
const mins = codexWindowMins(w)
|
|
1568
|
+
// duration 不明は分類できない → 未提供のままにする (位置で 5h/7d に決め打ちしない)。
|
|
1569
|
+
if (mins === null) continue
|
|
1570
|
+
// 最も近い正準窓のスロットへ割り当てる。
|
|
1571
|
+
let slot = null
|
|
1572
|
+
let slotDist = Infinity
|
|
1573
|
+
for (const s of CODEX_RATE_LIMIT_SLOTS) {
|
|
1574
|
+
const d = Math.abs(mins - s.canonicalMins)
|
|
1575
|
+
if (d < slotDist) {
|
|
1576
|
+
slotDist = d
|
|
1577
|
+
slot = s.key
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
// 同一スロットに複数窓が来たら、正準窓長により近い方を採用 (先着でなく最近傍)。
|
|
1581
|
+
if (slotDist >= bestDist[slot]) continue
|
|
1582
|
+
bestDist[slot] = slotDist
|
|
1583
|
+
result[slot] = {
|
|
1584
|
+
tokens: 0,
|
|
1585
|
+
messages: 0,
|
|
1586
|
+
limit: CODEX_DEFAULT_LIMIT,
|
|
1587
|
+
percent: usedPercent,
|
|
1588
|
+
provided: true,
|
|
1589
|
+
windowMinutes: mins,
|
|
1590
|
+
oldestTsMs: null,
|
|
1591
|
+
resetAtMs: codexWindowResetMs(w),
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return result
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1486
1597
|
/**
|
|
1487
1598
|
* 指定 threadId の Codex セッションから usage (5h/7d + context%) を返す。
|
|
1488
1599
|
* Claude の `getUsage()` と同じ shape (`UsageStats`) を返すため、frontend / wire
|
|
@@ -1525,29 +1636,18 @@ export async function codexUsageForThread(threadId) {
|
|
|
1525
1636
|
contextPercent = Math.min(100, Math.round((tokens / windowSize) * 1000) / 10)
|
|
1526
1637
|
}
|
|
1527
1638
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
resetMs = w.resets_at < 1e12 ? w.resets_at * 1000 : w.resets_at
|
|
1533
|
-
}
|
|
1534
|
-
return {
|
|
1535
|
-
tokens: 0,
|
|
1536
|
-
messages: 0,
|
|
1537
|
-
limit: CODEX_DEFAULT_LIMIT,
|
|
1538
|
-
percent: w.used_percent,
|
|
1539
|
-
oldestTsMs: null,
|
|
1540
|
-
resetAtMs: resetMs,
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1639
|
+
// T06407: 位置固定 (primary→5h / secondary→7d) をやめ、windowDurationMins で分類する。
|
|
1640
|
+
// rollout の rate_limits は snake_case (window_minutes / used_percent / resets_at) だが
|
|
1641
|
+
// classifyCodexRateLimitWindows が両 casing を吸収する。欠落窓は 0% でなく「未提供」。
|
|
1642
|
+
const { last5h, last7d } = classifyCodexRateLimitWindows(rateLimits)
|
|
1543
1643
|
|
|
1544
1644
|
return {
|
|
1545
1645
|
plan: rateLimits?.plan_type || "codex",
|
|
1546
1646
|
computedAt: Date.now(),
|
|
1547
1647
|
source: rateLimits ? "official" : "estimate",
|
|
1548
1648
|
context: contextPercent,
|
|
1549
|
-
last5h
|
|
1550
|
-
last7d
|
|
1649
|
+
last5h,
|
|
1650
|
+
last7d,
|
|
1551
1651
|
}
|
|
1552
1652
|
}
|
|
1553
1653
|
|