@cocorograph/hub-agent 0.6.8 → 0.6.9
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/main.mjs +10 -1
- package/src/usage.mjs +64 -1
package/package.json
CHANGED
package/src/main.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
listWorktreeStubs,
|
|
38
38
|
removeWorktree as removeWorktreeDir,
|
|
39
39
|
} from "./tmux.mjs"
|
|
40
|
-
import { getSessionUsages, getUsage } from "./usage.mjs"
|
|
40
|
+
import { getSessionUsages, getUsage, recordChatRateLimit } from "./usage.mjs"
|
|
41
41
|
|
|
42
42
|
const logger = pino({ name: "hub-agent" })
|
|
43
43
|
|
|
@@ -179,6 +179,15 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
|
|
|
179
179
|
// claude.error を返す経路に分岐 (dispatch 側で判定)。
|
|
180
180
|
if (claudeBridge) {
|
|
181
181
|
claudeBridge.on("event", ({ stream_id, session_id, event }) => {
|
|
182
|
+
// SDK の rate_limit_event を捕捉して usage の 5h/7d 実値に反映する
|
|
183
|
+
// (チャットモードでは statusLine が更新されないため、これが live 値の唯一の源)。
|
|
184
|
+
if (event?.type === "rate_limit_event" && event.rate_limit_info) {
|
|
185
|
+
try {
|
|
186
|
+
recordChatRateLimit(event.rate_limit_info)
|
|
187
|
+
} catch {
|
|
188
|
+
/* ignore */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
182
191
|
client.send({ type: "claude.event", stream_id, session_id, event })
|
|
183
192
|
})
|
|
184
193
|
claudeBridge.on("permission", ({ stream_id, request_id, tool_name, input }) => {
|
package/src/usage.mjs
CHANGED
|
@@ -107,6 +107,42 @@ async function readOrNull(p) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// チャット(SDK) の rate_limit_event から取得した最新 rate-limit (プロセス内共有)。
|
|
112
|
+
// statusLine cache はターミナルでしか更新されないため、チャットモードでは
|
|
113
|
+
// SDK stream の rate_limit_event を捕捉してここに溜め、getUsage で 5h/7d に反映する。
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
const chatRateLimits = {
|
|
116
|
+
/** @type {{percent:number, resetAtMs:number|null}|null} */ five_hour: null,
|
|
117
|
+
/** @type {{percent:number, resetAtMs:number|null}|null} */ seven_day: null,
|
|
118
|
+
updatedAtMs: 0,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** utilization (0-1 割合 or 0-100 %) を % に正規化。 */
|
|
122
|
+
function utilizationToPercent(u) {
|
|
123
|
+
if (typeof u !== "number" || !Number.isFinite(u)) return null
|
|
124
|
+
return u <= 1 ? Math.round(u * 1000) / 10 : Math.round(u * 10) / 10
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* SDK の SDKRateLimitInfo を取り込む (main.mjs の rate_limit_event ハンドラから呼ぶ)。
|
|
129
|
+
* rateLimitType: 'five_hour' / 'seven_day*' を 5h / 7d スロットに振り分ける。
|
|
130
|
+
*/
|
|
131
|
+
export function recordChatRateLimit(info) {
|
|
132
|
+
if (!info || typeof info !== "object") return
|
|
133
|
+
const type = info.rateLimitType
|
|
134
|
+
const slot = type === "five_hour" ? "five_hour" : typeof type === "string" && type.startsWith("seven_day") ? "seven_day" : null
|
|
135
|
+
if (!slot) return // overage 等は対象外
|
|
136
|
+
const percent = utilizationToPercent(info.utilization)
|
|
137
|
+
if (percent === null) return
|
|
138
|
+
let resetAtMs = null
|
|
139
|
+
if (typeof info.resetsAt === "number") {
|
|
140
|
+
resetAtMs = info.resetsAt < 1e12 ? info.resetsAt * 1000 : info.resetsAt
|
|
141
|
+
}
|
|
142
|
+
chatRateLimits[slot] = { percent, resetAtMs }
|
|
143
|
+
chatRateLimits.updatedAtMs = Date.now()
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
async function readOfficial(now) {
|
|
111
147
|
const text = await readOrNull(statuslineCache())
|
|
112
148
|
if (!text) return null
|
|
@@ -339,10 +375,10 @@ export async function getUsage() {
|
|
|
339
375
|
const now = Date.now()
|
|
340
376
|
const official = await readOfficial(now)
|
|
341
377
|
const result = official || (await readEstimate(now))
|
|
378
|
+
const cacheMtime = await statuslineCacheMtime()
|
|
342
379
|
|
|
343
380
|
const est = await latestJsonlContext(now)
|
|
344
381
|
if (est) {
|
|
345
|
-
const cacheMtime = await statuslineCacheMtime()
|
|
346
382
|
// statusLine が jsonl と同程度以上に新しければ official.context が信頼できる
|
|
347
383
|
// (ターミナルモード)。jsonl の方が新しい (チャットモード) なら推定で上書き。
|
|
348
384
|
const officialFresh =
|
|
@@ -354,6 +390,33 @@ export async function getUsage() {
|
|
|
354
390
|
result.context = est.percent
|
|
355
391
|
}
|
|
356
392
|
}
|
|
393
|
+
|
|
394
|
+
// チャット(SDK) の rate_limit_event が statusLine cache より新しければ、5h/7d を
|
|
395
|
+
// その実値 (utilization) で上書きする。ターミナルが statusLine を更新していない
|
|
396
|
+
// チャットモードでも 5h/7d がライブで動くようになる。
|
|
397
|
+
if (
|
|
398
|
+
chatRateLimits.updatedAtMs > 0 &&
|
|
399
|
+
(cacheMtime === null || chatRateLimits.updatedAtMs > cacheMtime)
|
|
400
|
+
) {
|
|
401
|
+
if (chatRateLimits.five_hour) {
|
|
402
|
+
result.last5h = {
|
|
403
|
+
...result.last5h,
|
|
404
|
+
percent: chatRateLimits.five_hour.percent,
|
|
405
|
+
resetAtMs: chatRateLimits.five_hour.resetAtMs ?? result.last5h?.resetAtMs ?? null,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (chatRateLimits.seven_day) {
|
|
409
|
+
result.last7d = {
|
|
410
|
+
...result.last7d,
|
|
411
|
+
percent: chatRateLimits.seven_day.percent,
|
|
412
|
+
resetAtMs: chatRateLimits.seven_day.resetAtMs ?? result.last7d?.resetAtMs ?? null,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (chatRateLimits.five_hour || chatRateLimits.seven_day) {
|
|
416
|
+
// rate_limit_event は API 実値なので official 扱い。
|
|
417
|
+
result.source = "official"
|
|
418
|
+
}
|
|
419
|
+
}
|
|
357
420
|
return result
|
|
358
421
|
}
|
|
359
422
|
|