@songsid/agend 2.3.4-beta.8 → 2.3.4-beta.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/THIRD_PARTY_NOTICES.md +37 -0
- package/dist/agent-cli-instructions.md +1 -1
- package/dist/agent-credentials.d.ts +24 -0
- package/dist/agent-credentials.js +66 -0
- package/dist/agent-credentials.js.map +1 -0
- package/dist/agent-endpoint.d.ts +3 -1
- package/dist/agent-endpoint.js +13 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +28 -4
- package/dist/authority/session-authorizations.js +63 -17
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/backend/antigravity.js +3 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex-footer-hints.d.ts +4 -0
- package/dist/backend/codex-footer-hints.js +27 -0
- package/dist/backend/codex-footer-hints.js.map +1 -0
- package/dist/backend/codex.js +8 -3
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/muse.js +5 -3
- package/dist/backend/muse.js.map +1 -1
- package/dist/cache-ledger.d.ts +85 -0
- package/dist/cache-ledger.js +236 -1
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +5 -0
- package/dist/cache-service.js +16 -4
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +11 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +4 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +10 -0
- package/dist/cross-instance-notice.js +67 -5
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +11 -4
- package/dist/daemon.js.map +1 -1
- package/dist/export-import.d.ts +7 -0
- package/dist/export-import.js +44 -10
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +82 -4
- package/dist/fleet-manager.js +361 -29
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +1 -1
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/instance-lifecycle.d.ts +6 -2
- package/dist/instance-lifecycle.js +11 -6
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-topic-delete.d.ts +77 -0
- package/dist/instance-topic-delete.js +123 -0
- package/dist/instance-topic-delete.js.map +1 -0
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +1 -1
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +23 -1
- package/dist/locale.js.map +1 -1
- package/dist/log-steps.d.ts +4 -3
- package/dist/log-steps.js +32 -7
- package/dist/log-steps.js.map +1 -1
- package/dist/outbound-handlers.js +3 -3
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.js +5 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/scheduler/db.d.ts +2 -1
- package/dist/scheduler/db.js +23 -9
- package/dist/scheduler/db.js.map +1 -1
- package/dist/settings-api.d.ts +13 -0
- package/dist/settings-api.js +12 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/tool-progress.d.ts +2 -0
- package/dist/tool-progress.js +19 -3
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.js +4 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-usage.js +90 -27
- package/dist/ui/panel-chat.js +2 -0
- package/dist/ui/settings-strings.js +2 -2
- package/dist/ui/shared/app.css +15 -0
- package/dist/ui/shared/usage-dialog.js +18 -1
- package/dist/ui/shared/view-strings.js +16 -0
- package/dist/usage/capacity.d.ts +69 -0
- package/dist/usage/capacity.js +119 -0
- package/dist/usage/capacity.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +3 -0
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/profile-timeline.d.ts +16 -0
- package/dist/usage/profile-timeline.js +58 -0
- package/dist/usage/profile-timeline.js.map +1 -0
- package/dist/usage/quota-history.d.ts +43 -0
- package/dist/usage/quota-history.js +168 -0
- package/dist/usage/quota-history.js.map +1 -0
- package/dist/usage/usage-api.d.ts +31 -0
- package/dist/usage/usage-api.js +80 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.d.ts +25 -1
- package/dist/usage-report.js +55 -4
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +4 -1
- package/dist/usage-rollup.js +12 -3
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +6 -0
- package/dist/web-api.js +8 -2
- package/dist/web-api.js.map +1 -1
- package/dist/web-session-authorization.d.ts +13 -2
- package/dist/web-session-authorization.js +10 -3
- package/dist/web-session-authorization.js.map +1 -1
- package/package.json +1 -1
|
@@ -166,7 +166,7 @@ register("settings", {
|
|
|
166
166
|
failed: "Failed",
|
|
167
167
|
actionFailed: "{0} failed",
|
|
168
168
|
noAgentMatch: "No agents match \"{0}\".",
|
|
169
|
-
deleteAgent: "Delete agent \"{0}\"? This will be applied only after you confirm the pending changes.",
|
|
169
|
+
deleteAgent: "Delete agent \"{0}\" and its chat topic (on Discord, its channel and history)? This will be applied only after you confirm the pending changes.",
|
|
170
170
|
nameRequired: "name required",
|
|
171
171
|
tokenEnvRequired: "token env required",
|
|
172
172
|
removeBot: "Remove bot \"{0}\"? (the token in .env is left untouched)",
|
|
@@ -823,7 +823,7 @@ register("settings", {
|
|
|
823
823
|
failed: "失敗",
|
|
824
824
|
actionFailed: "{0}失敗",
|
|
825
825
|
noAgentMatch: "沒有符合「{0}」的 Agent。",
|
|
826
|
-
deleteAgent: "確定刪除 Agent「{0}
|
|
826
|
+
deleteAgent: "確定刪除 Agent「{0}」及它的聊天 topic(在 Discord 上是它的頻道與訊息記錄)嗎?只有在確認套用待處理變更後才會生效。",
|
|
827
827
|
nameRequired: "必須填寫名稱",
|
|
828
828
|
tokenEnvRequired: "必須填寫 token 環境變數",
|
|
829
829
|
removeBot: "確定移除機器人「{0}」嗎?(.env 中的 token 會保留)",
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -1392,6 +1392,11 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
1392
1392
|
.u-stale { font-weight: var(--fw-semibold); }
|
|
1393
1393
|
.u-stale-detail { color: var(--text-3); font-size: var(--fs-xs); }
|
|
1394
1394
|
.u-sub { color: var(--text-3); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
|
|
1395
|
+
/* #3066 §1: a weekly quota's measured 100% under its bar, and its notes (the fleet-only limit always). */
|
|
1396
|
+
.u-cap { margin-top: 4px; font-size: var(--fs-xs); }
|
|
1397
|
+
.u-cap-line { color: var(--text-2); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
1398
|
+
.u-cap-wait .u-cap-line { color: var(--text-3); }
|
|
1399
|
+
.u-cap-note { color: var(--text-3); overflow-wrap: anywhere; }
|
|
1395
1400
|
.u-err { color: var(--warn); font-size: var(--fs-sm); }
|
|
1396
1401
|
.u-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
|
|
1397
1402
|
/* #1673: the usage rail beside the page on a wide screen (header-tools.js UsageRail; shown only at min-width 1200px). */
|
|
@@ -2111,6 +2116,16 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
|
|
|
2111
2116
|
.us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
|
|
2112
2117
|
.us-est { display: block; font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-3); }
|
|
2113
2118
|
.us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
|
|
2119
|
+
/* #3066 §3: a row's numbers carry small marks (◐ partly priced, † the CLI's amount, ≈ estimated) and "—" with a reason,
|
|
2120
|
+
each said in its tooltip; the key under the table names only the marks that appear. */
|
|
2121
|
+
.us-mark { margin-left: 2px; color: var(--text-3); font-size: var(--fs-xs); cursor: help; }
|
|
2122
|
+
.us-mark.us-approx { margin-left: 0; margin-right: 1px; }
|
|
2123
|
+
.us-none { cursor: help; }
|
|
2124
|
+
.us-key { margin-top: 6px; }
|
|
2125
|
+
/* #3066 §2: each model's speed after the first token. */
|
|
2126
|
+
.us-speed { margin-top: 12px; }
|
|
2127
|
+
.us-speed .row-item { gap: 8px; flex-wrap: wrap; }
|
|
2128
|
+
.us-speed .num { font-variant-numeric: tabular-nums; }
|
|
2114
2129
|
.us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
|
|
2115
2130
|
.us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
|
|
2116
2131
|
.us-scroll { overflow-x: auto; max-width: 100%; }
|
|
@@ -64,6 +64,22 @@ function expiryText(iso) {
|
|
|
64
64
|
if (!(ms > 0)) return "";
|
|
65
65
|
return usageText("", { key: "usage.ticket_expiry", args: [`${at.getMonth() + 1}/${at.getDate()}`, usageDuration(ms)] });
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* #3066 §1: a server line ({ text, i18n }) in this page's language — or, when this tab's strings predate the key (a
|
|
69
|
+
* stale tab), the server's own English text, never the bare key.
|
|
70
|
+
*/
|
|
71
|
+
export function capacityLine(x) {
|
|
72
|
+
if (!x || typeof x.text !== "string") return "";
|
|
73
|
+
const key = x.i18n && typeof x.i18n.key === "string" ? `view.${x.i18n.key}` : null;
|
|
74
|
+
return key && t(key) !== key ? usageText(x.text, x.i18n) : x.text;
|
|
75
|
+
}
|
|
76
|
+
/** #3066 §1: a weekly quota's measured 100% (or why not yet), and the limit it always carries: only this fleet counts. */
|
|
77
|
+
function UsageCapacity({ c }) {
|
|
78
|
+
const notes = Array.isArray(c.notes) ? c.notes : [];
|
|
79
|
+
return html`<div class=${`u-cap${c.status === "ok" ? "" : " u-cap-wait"}`}>
|
|
80
|
+
<div class="u-cap-line">${capacityLine(c.line)}</div>
|
|
81
|
+
${notes.map((n, i) => html`<div key=${i} class="u-cap-note">${capacityLine(n)}</div>`)}</div>`;
|
|
82
|
+
}
|
|
67
83
|
export function UsageMetric({ m }) {
|
|
68
84
|
const bar = useRef(null);
|
|
69
85
|
const label = usageText(m.label, m.labelI18n);
|
|
@@ -78,7 +94,8 @@ export function UsageMetric({ m }) {
|
|
|
78
94
|
const sub = [note, resetText(m.resetsAt)].filter(Boolean).join(" · ");
|
|
79
95
|
return html`<div class="u-metric"><div class="u-row"><span class="u-label">${label}</span>${word ? html`<span class=${`u-status ${cls}`}>${word}</span>` : null}
|
|
80
96
|
<span class=${`u-pct ${cls}`}>${pct.toFixed(0)}<span class="u-unit">%</span></span></div>
|
|
81
|
-
<div class="u-meter"><div ref=${bar} class=${`u-fill ${cls}`}></div></div>${sub ? html`<div class="u-sub">${sub}</div>` : null}
|
|
97
|
+
<div class="u-meter"><div ref=${bar} class=${`u-fill ${cls}`}></div></div>${sub ? html`<div class="u-sub">${sub}</div>` : null}
|
|
98
|
+
${m.capacity ? html`<${UsageCapacity} c=${m.capacity} />` : null}</div>`;
|
|
82
99
|
}
|
|
83
100
|
if (m.type === "dollars") {
|
|
84
101
|
const val = m.limit ? `$${(m.used ?? 0).toFixed(2)} / $${m.limit.toFixed(2)}` : `$${(m.used ?? 0).toFixed(2)}`;
|
|
@@ -154,6 +154,14 @@ register("view", {
|
|
|
154
154
|
"usage.stale.transient": "not updated · cached at {0}",
|
|
155
155
|
"usage.stale.error": "couldn't read · cached at {0}",
|
|
156
156
|
"usage.metric.reset_since": "reset since {0}",
|
|
157
|
+
"usage.capacity.line": "100% ≈ {0} ({1}–{2}) · ≈ {3} tokens · from {4} weeks",
|
|
158
|
+
"usage.capacity.line_tokens": "100% ≈ {0} tokens ({1}–{2}) · from {3} weeks",
|
|
159
|
+
"usage.capacity.not_enough": "Not enough usage yet to measure 100% (Δ {0}% of 10%)",
|
|
160
|
+
"usage.capacity.only_agend": "Counts only this fleet's agents; use of the same account elsewhere makes this low.",
|
|
161
|
+
"usage.capacity.mixed": "Bases disagree — possibly used outside this fleet",
|
|
162
|
+
"usage.capacity.best_fit_api": "API-equivalent fits best: ±{0}% across {1} weeks",
|
|
163
|
+
"usage.capacity.best_fit_output": "Output tokens fit best: ±{0}% across {1} weeks",
|
|
164
|
+
"usage.capacity.best_fit_all": "All tokens fit best: ±{0}% across {1} weeks",
|
|
157
165
|
"usage.error.http": "Usage request failed (HTTP {0}).",
|
|
158
166
|
"usage.error.billing_http": "Billing request failed (HTTP {0}).",
|
|
159
167
|
"usage.error.invalid_response": "Invalid response from usage endpoint.",
|
|
@@ -414,6 +422,14 @@ register("view", {
|
|
|
414
422
|
"usage.stale.transient": "暫時無法更新 · 快取於 {0}",
|
|
415
423
|
"usage.stale.error": "讀取失敗 · 快取於 {0}",
|
|
416
424
|
"usage.metric.reset_since": "已於 {0} 重置",
|
|
425
|
+
"usage.capacity.line": "100% ≈ {0}({1}–{2})· 約 {3} token · 依 {4} 週",
|
|
426
|
+
"usage.capacity.line_tokens": "100% ≈ {0} token({1}–{2})· 依 {3} 週",
|
|
427
|
+
"usage.capacity.not_enough": "用量還不夠估算 100%(已用 Δ {0}%,需 10%)",
|
|
428
|
+
"usage.capacity.only_agend": "只計算這個 fleet 的 agent;同一帳號在別處的使用會讓這個值偏低。",
|
|
429
|
+
"usage.capacity.mixed": "各換算方式不一致——可能有 fleet 以外的使用",
|
|
430
|
+
"usage.capacity.best_fit_api": "「API 等值」最吻合:{1} 週內 ±{0}%",
|
|
431
|
+
"usage.capacity.best_fit_output": "「輸出 token」最吻合:{1} 週內 ±{0}%",
|
|
432
|
+
"usage.capacity.best_fit_all": "「全部 token」最吻合:{1} 週內 ±{0}%",
|
|
417
433
|
"usage.error.http": "用量請求失敗(HTTP {0})。",
|
|
418
434
|
"usage.error.billing_http": "計費請求失敗(HTTP {0})。",
|
|
419
435
|
"usage.error.invalid_response": "用量端點回傳了無效資料。",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3066 §1: how much is 100% of an account's weekly quota, measured — what this account's agents used while the vendor's
|
|
3
|
+
* weekly percent moved (the readings of quota-history.ts, the hours of the usage ledger). Pure: the fleet gathers both.
|
|
4
|
+
*
|
|
5
|
+
* Within one reset window (the same `resetsAt`, the percent never going down) the first and last readings bound an
|
|
6
|
+
* interval: capacity = consumed in it ÷ Δ%. Two uncertainties, both carried into the range, never hidden:
|
|
7
|
+
* - the vendors report whole percents, so Δ is ±1: the range divides by Δ+1 … Δ−1;
|
|
8
|
+
* - the ledger is hourly, so the two boundary hours are uncertain: without them is the low end, with them the high.
|
|
9
|
+
* A window counts only when Δ ≥ 10 points (rounding then costs ≤ ~±10%). Several windows: the median of their midpoints,
|
|
10
|
+
* the range their lowest low … highest high.
|
|
11
|
+
*
|
|
12
|
+
* Three bases, because the vendor's weighting is not published: the API-equivalent at list price (likely what the quota
|
|
13
|
+
* tracks — it weighs Opus over Sonnet, cache reads far below input), output tokens, all tokens. With three windows or
|
|
14
|
+
* more, the basis whose capacity varies least across weeks (coefficient of variation) is named as the best fit.
|
|
15
|
+
*
|
|
16
|
+
* What it cannot see: the same account used outside this fleet (claude.ai, another machine) spends the quota without a
|
|
17
|
+
* token here, so the estimate comes out LOW; a window whose bases disagree with each other by more than 2× is marked
|
|
18
|
+
* `mixed`. The page says so (usage.capacity.only_agend, usage.capacity.mixed).
|
|
19
|
+
*/
|
|
20
|
+
import { type QuotaReading } from "./quota-history.js";
|
|
21
|
+
export declare const CAPACITY_MIN_DELTA = 10;
|
|
22
|
+
export declare const BASES: readonly ["api", "output", "all"];
|
|
23
|
+
export type CapacityBasis = typeof BASES[number];
|
|
24
|
+
/** One hour of one account's usage, on each basis (api null: a model in it has no list price). */
|
|
25
|
+
export interface CapacityHour {
|
|
26
|
+
hour: number;
|
|
27
|
+
api: number | null;
|
|
28
|
+
output: number;
|
|
29
|
+
all: number;
|
|
30
|
+
}
|
|
31
|
+
/** `windows`: how many reset windows this basis was measured over (a basis can miss one: an unpriced hour). */
|
|
32
|
+
export interface BasisEstimate {
|
|
33
|
+
median: number;
|
|
34
|
+
low: number;
|
|
35
|
+
high: number;
|
|
36
|
+
cv: number | null;
|
|
37
|
+
windows: number;
|
|
38
|
+
}
|
|
39
|
+
export type CapacityEstimate = {
|
|
40
|
+
status: "ok";
|
|
41
|
+
windows: number;
|
|
42
|
+
deltaPct: number;
|
|
43
|
+
bases: Partial<Record<CapacityBasis, BasisEstimate>>;
|
|
44
|
+
bestFit: CapacityBasis | null;
|
|
45
|
+
mixed: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
status: "not_enough";
|
|
48
|
+
deltaPct: number;
|
|
49
|
+
};
|
|
50
|
+
interface Window {
|
|
51
|
+
t1: number;
|
|
52
|
+
p1: number;
|
|
53
|
+
t2: number;
|
|
54
|
+
p2: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The reset windows the readings span: a new `resetsAt`, or a percent that went down, starts the next. "New" is
|
|
58
|
+
* quota-history's sameReset (Prism #3073 P2): a vendor that gives only "resets in N seconds" drifts by seconds between
|
|
59
|
+
* reads and is still one window; compared with the window's latest reading, a real reset moves it by days.
|
|
60
|
+
*/
|
|
61
|
+
export declare function quotaWindows(readings: readonly QuotaReading[]): Window[];
|
|
62
|
+
/**
|
|
63
|
+
* The estimate for one account's one weekly metric. `available(from, to)`: whether every hour from `from` to `to` is in
|
|
64
|
+
* the ledger — kept and scanned (Prism #3073 P2): the quota history keeps 12 weeks, the hours 31 days, so a window
|
|
65
|
+
* outside them would count as zero use and the estimate would come out absurdly low. Such a window is left out.
|
|
66
|
+
* Null: there are windows to measure, but no usage of this account in any of them on any basis — nothing to show.
|
|
67
|
+
*/
|
|
68
|
+
export declare function estimateCapacity(readings: readonly QuotaReading[], hours: readonly CapacityHour[], available?: (from: number, to: number) => boolean): CapacityEstimate | null;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3066 §1: how much is 100% of an account's weekly quota, measured — what this account's agents used while the vendor's
|
|
3
|
+
* weekly percent moved (the readings of quota-history.ts, the hours of the usage ledger). Pure: the fleet gathers both.
|
|
4
|
+
*
|
|
5
|
+
* Within one reset window (the same `resetsAt`, the percent never going down) the first and last readings bound an
|
|
6
|
+
* interval: capacity = consumed in it ÷ Δ%. Two uncertainties, both carried into the range, never hidden:
|
|
7
|
+
* - the vendors report whole percents, so Δ is ±1: the range divides by Δ+1 … Δ−1;
|
|
8
|
+
* - the ledger is hourly, so the two boundary hours are uncertain: without them is the low end, with them the high.
|
|
9
|
+
* A window counts only when Δ ≥ 10 points (rounding then costs ≤ ~±10%). Several windows: the median of their midpoints,
|
|
10
|
+
* the range their lowest low … highest high.
|
|
11
|
+
*
|
|
12
|
+
* Three bases, because the vendor's weighting is not published: the API-equivalent at list price (likely what the quota
|
|
13
|
+
* tracks — it weighs Opus over Sonnet, cache reads far below input), output tokens, all tokens. With three windows or
|
|
14
|
+
* more, the basis whose capacity varies least across weeks (coefficient of variation) is named as the best fit.
|
|
15
|
+
*
|
|
16
|
+
* What it cannot see: the same account used outside this fleet (claude.ai, another machine) spends the quota without a
|
|
17
|
+
* token here, so the estimate comes out LOW; a window whose bases disagree with each other by more than 2× is marked
|
|
18
|
+
* `mixed`. The page says so (usage.capacity.only_agend, usage.capacity.mixed).
|
|
19
|
+
*/
|
|
20
|
+
import { sameReset } from "./quota-history.js";
|
|
21
|
+
export const CAPACITY_MIN_DELTA = 10;
|
|
22
|
+
export const BASES = ["api", "output", "all"];
|
|
23
|
+
const H = 3_600_000;
|
|
24
|
+
/**
|
|
25
|
+
* The reset windows the readings span: a new `resetsAt`, or a percent that went down, starts the next. "New" is
|
|
26
|
+
* quota-history's sameReset (Prism #3073 P2): a vendor that gives only "resets in N seconds" drifts by seconds between
|
|
27
|
+
* reads and is still one window; compared with the window's latest reading, a real reset moves it by days.
|
|
28
|
+
*/
|
|
29
|
+
export function quotaWindows(readings) {
|
|
30
|
+
const out = [];
|
|
31
|
+
let cur = null;
|
|
32
|
+
for (const r of [...readings].sort((a, b) => a.t - b.t)) {
|
|
33
|
+
if (!cur || !sameReset(r.resetsAt, cur.resetsAt) || r.used < cur.p2) {
|
|
34
|
+
if (cur)
|
|
35
|
+
out.push(cur);
|
|
36
|
+
cur = { t1: r.t, p1: r.used, t2: r.t, p2: r.used, resetsAt: r.resetsAt };
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
cur.t2 = r.t;
|
|
40
|
+
cur.p2 = r.used;
|
|
41
|
+
cur.resetsAt = r.resetsAt;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (cur)
|
|
45
|
+
out.push(cur);
|
|
46
|
+
return out.map(({ t1, p1, t2, p2 }) => ({ t1, p1, t2, p2 }));
|
|
47
|
+
}
|
|
48
|
+
const median = (xs) => { const s = [...xs].sort((a, b) => a - b), m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; };
|
|
49
|
+
const cv = (xs) => {
|
|
50
|
+
if (xs.length < 2)
|
|
51
|
+
return null;
|
|
52
|
+
const mean = xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
53
|
+
return mean > 0 ? Math.sqrt(xs.reduce((a, x) => a + (x - mean) ** 2, 0) / xs.length) / mean : null;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The estimate for one account's one weekly metric. `available(from, to)`: whether every hour from `from` to `to` is in
|
|
57
|
+
* the ledger — kept and scanned (Prism #3073 P2): the quota history keeps 12 weeks, the hours 31 days, so a window
|
|
58
|
+
* outside them would count as zero use and the estimate would come out absurdly low. Such a window is left out.
|
|
59
|
+
* Null: there are windows to measure, but no usage of this account in any of them on any basis — nothing to show.
|
|
60
|
+
*/
|
|
61
|
+
export function estimateCapacity(readings, hours, available = () => true) {
|
|
62
|
+
const windows = quotaWindows(readings).filter((w) => available(Math.floor(w.t1 / H) * H, w.t2));
|
|
63
|
+
const usable = windows.filter((w) => w.p2 - w.p1 >= CAPACITY_MIN_DELTA);
|
|
64
|
+
if (!usable.length) {
|
|
65
|
+
// No window inside the ledger at all: "not enough usage" would be a claim about weeks it cannot see. Say nothing.
|
|
66
|
+
if (!windows.length)
|
|
67
|
+
return null;
|
|
68
|
+
return { status: "not_enough", deltaPct: Math.max(0, ...windows.map((w) => w.p2 - w.p1)) };
|
|
69
|
+
}
|
|
70
|
+
const per = usable.map((w) => {
|
|
71
|
+
const delta = w.p2 - w.p1;
|
|
72
|
+
const inside = hours.filter((h) => h.hour >= w.t1 && h.hour + H <= w.t2); // whole hours within: low
|
|
73
|
+
const touching = hours.filter((h) => h.hour + H > w.t1 && h.hour < w.t2); // any hour touching: high
|
|
74
|
+
const sum = (list, b) => {
|
|
75
|
+
let n = 0;
|
|
76
|
+
for (const h of list) {
|
|
77
|
+
const v = h[b];
|
|
78
|
+
if (v === null)
|
|
79
|
+
return null;
|
|
80
|
+
n += v;
|
|
81
|
+
}
|
|
82
|
+
return n;
|
|
83
|
+
};
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const b of BASES) {
|
|
86
|
+
const lo = sum(inside, b), hi = sum(touching, b);
|
|
87
|
+
if (lo === null || hi === null || hi <= 0)
|
|
88
|
+
continue; // a basis with an unpriced hour, or nothing used
|
|
89
|
+
out[b] = { low: lo / ((delta + 1) / 100), mid: ((lo + hi) / 2) / (delta / 100), high: hi / ((delta - 1) / 100) };
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
});
|
|
93
|
+
const bases = {};
|
|
94
|
+
for (const b of BASES) {
|
|
95
|
+
const ws = per.map((p) => p[b]).filter((x) => !!x);
|
|
96
|
+
if (!ws.length)
|
|
97
|
+
continue;
|
|
98
|
+
const mids = ws.map((x) => x.mid);
|
|
99
|
+
bases[b] = { median: median(mids), low: Math.min(...ws.map((x) => x.low)), high: Math.max(...ws.map((x) => x.high)), cv: cv(mids), windows: ws.length };
|
|
100
|
+
}
|
|
101
|
+
if (!BASES.some((b) => bases[b]))
|
|
102
|
+
return null; // Prism #3073 P1: no basis, no estimate
|
|
103
|
+
// Best fit: with three windows or more, the basis that varies least across them.
|
|
104
|
+
const scored = BASES.filter((b) => bases[b]?.cv !== null && bases[b]?.cv !== undefined && bases[b].windows >= 3);
|
|
105
|
+
const bestFit = scored.length ? scored.reduce((a, b) => (bases[b].cv < bases[a].cv ? b : a)) : null;
|
|
106
|
+
// Mixed: in some window the API-equivalent and the token count disagree (each against its own median) by over 2×.
|
|
107
|
+
let mixed = false;
|
|
108
|
+
if (bases.api && bases.all && usable.length >= 2) {
|
|
109
|
+
for (const p of per) {
|
|
110
|
+
if (!p.api || !p.all)
|
|
111
|
+
continue;
|
|
112
|
+
const ra = p.api.mid / bases.api.median, rt = p.all.mid / bases.all.median;
|
|
113
|
+
if (Math.max(ra / rt, rt / ra) > 2)
|
|
114
|
+
mixed = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { status: "ok", windows: usable.length, deltaPct: Math.max(...usable.map((w) => w.p2 - w.p1)), bases, bestFit, mixed };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=capacity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capacity.js","sourceRoot":"","sources":["../../src/usage/capacity.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Keep this list explicit: the parity test rejects a key missing from either
|
|
4
4
|
* locale surface before a mixed-language release can ship.
|
|
5
5
|
*/
|
|
6
|
-
export declare const USAGE_I18N_KEYS: readonly ["usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in", "usage.default_login", "usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon", "usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes", "usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window", "usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly", "usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage", "usage.metric.rate_limit_resets", "usage.ticket_expiry", "usage.metric.credits", "usage.metric.weekly_limit", "usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges", "usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits", "usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes", "usage.value.disabled", "usage.value.cap", "usage.value.unlimited", "usage.note.binding", "usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models", "usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly", "usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key", "usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota", "usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok", "usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out", "usage.hint.claude_profile_signed_out", "usage.hint.claude_profile_signed_out_chat", "usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account", "usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited", "usage.error.rate_limited_transient", "usage.stale_rate_limited", "usage.stale.expired", "usage.stale.transient", "usage.stale.error", "usage.metric.reset_since", "usage.error.http", "usage.error.billing_http", "usage.error.invalid_response", "usage.error.invalid_billing_response", "usage.error.invalid_kiro_response", "usage.error.claude_token_expired", "usage.error.codex_token_expired", "usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap", "usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired", "usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh", "usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist"];
|
|
6
|
+
export declare const USAGE_I18N_KEYS: readonly ["usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in", "usage.default_login", "usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon", "usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes", "usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window", "usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly", "usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage", "usage.metric.rate_limit_resets", "usage.ticket_expiry", "usage.metric.credits", "usage.metric.weekly_limit", "usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges", "usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits", "usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes", "usage.value.disabled", "usage.value.cap", "usage.value.unlimited", "usage.note.binding", "usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models", "usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly", "usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key", "usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota", "usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok", "usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out", "usage.hint.claude_profile_signed_out", "usage.hint.claude_profile_signed_out_chat", "usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account", "usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited", "usage.error.rate_limited_transient", "usage.stale_rate_limited", "usage.stale.expired", "usage.stale.transient", "usage.stale.error", "usage.metric.reset_since", "usage.error.http", "usage.error.billing_http", "usage.error.invalid_response", "usage.error.invalid_billing_response", "usage.error.invalid_kiro_response", "usage.error.claude_token_expired", "usage.error.codex_token_expired", "usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap", "usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired", "usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh", "usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist", "usage.capacity.line", "usage.capacity.line_tokens", "usage.capacity.not_enough", "usage.capacity.only_agend", "usage.capacity.mixed", "usage.capacity.best_fit_api", "usage.capacity.best_fit_output", "usage.capacity.best_fit_all"];
|
|
7
7
|
export type UsageI18nKey = typeof USAGE_I18N_KEYS[number];
|
package/dist/usage/i18n-keys.js
CHANGED
|
@@ -31,5 +31,8 @@ export const USAGE_I18N_KEYS = [
|
|
|
31
31
|
"usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired",
|
|
32
32
|
"usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh",
|
|
33
33
|
"usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist",
|
|
34
|
+
// #3066 §1: a weekly quota's measured 100%.
|
|
35
|
+
"usage.capacity.line", "usage.capacity.line_tokens", "usage.capacity.not_enough", "usage.capacity.only_agend", "usage.capacity.mixed",
|
|
36
|
+
"usage.capacity.best_fit_api", "usage.capacity.best_fit_output", "usage.capacity.best_fit_all",
|
|
34
37
|
];
|
|
35
38
|
//# sourceMappingURL=i18n-keys.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-keys.js","sourceRoot":"","sources":["../../src/usage/i18n-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB;IACtG,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB;IAC7E,2BAA2B,EAAE,8BAA8B,EAAE,wBAAwB;IACrF,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,0BAA0B;IACjG,oBAAoB,EAAE,4BAA4B,EAAE,2BAA2B;IAC/E,4BAA4B,EAAE,yBAAyB,EAAE,0BAA0B;IACnF,gCAAgC,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,2BAA2B;IAC5G,4BAA4B,EAAE,4BAA4B,EAAE,8BAA8B;IAC1F,0BAA0B,EAAE,sBAAsB,EAAE,oBAAoB;IACxE,4BAA4B,EAAE,4BAA4B,EAAE,wBAAwB;IACpF,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,oBAAoB;IACxF,6BAA6B,EAAE,0BAA0B,EAAE,2BAA2B;IACtF,gCAAgC,EAAE,wCAAwC,EAAE,uCAAuC;IACnH,2BAA2B,EAAE,+BAA+B,EAAE,2BAA2B;IACzF,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB;IACxE,wBAAwB,EAAE,2BAA2B,EAAE,uBAAuB;IAC9E,0BAA0B,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,sCAAsC,EAAE,2CAA2C;IACtK,6BAA6B,EAAE,mCAAmC,EAAE,6BAA6B;IACjG,yBAAyB,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,oCAAoC,EAAE,0BAA0B;IACrJ,qBAAqB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,0BAA0B;IAC/F,kBAAkB,EAAE,0BAA0B,EAAE,8BAA8B;IAC9E,sCAAsC,EAAE,mCAAmC;IAC3E,kCAAkC,EAAE,iCAAiC;IACrE,0BAA0B,EAAE,mCAAmC,EAAE,qBAAqB;IACtF,4BAA4B,EAAE,+BAA+B,EAAE,+BAA+B;IAC9F,+BAA+B,EAAE,gCAAgC,EAAE,6BAA6B;IAChG,sCAAsC,EAAE,kCAAkC,EAAE,0BAA0B;
|
|
1
|
+
{"version":3,"file":"i18n-keys.js","sourceRoot":"","sources":["../../src/usage/i18n-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB;IACtG,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB;IAC7E,2BAA2B,EAAE,8BAA8B,EAAE,wBAAwB;IACrF,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,0BAA0B;IACjG,oBAAoB,EAAE,4BAA4B,EAAE,2BAA2B;IAC/E,4BAA4B,EAAE,yBAAyB,EAAE,0BAA0B;IACnF,gCAAgC,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,2BAA2B;IAC5G,4BAA4B,EAAE,4BAA4B,EAAE,8BAA8B;IAC1F,0BAA0B,EAAE,sBAAsB,EAAE,oBAAoB;IACxE,4BAA4B,EAAE,4BAA4B,EAAE,wBAAwB;IACpF,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,oBAAoB;IACxF,6BAA6B,EAAE,0BAA0B,EAAE,2BAA2B;IACtF,gCAAgC,EAAE,wCAAwC,EAAE,uCAAuC;IACnH,2BAA2B,EAAE,+BAA+B,EAAE,2BAA2B;IACzF,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB;IACxE,wBAAwB,EAAE,2BAA2B,EAAE,uBAAuB;IAC9E,0BAA0B,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,sCAAsC,EAAE,2CAA2C;IACtK,6BAA6B,EAAE,mCAAmC,EAAE,6BAA6B;IACjG,yBAAyB,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,oCAAoC,EAAE,0BAA0B;IACrJ,qBAAqB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,0BAA0B;IAC/F,kBAAkB,EAAE,0BAA0B,EAAE,8BAA8B;IAC9E,sCAAsC,EAAE,mCAAmC;IAC3E,kCAAkC,EAAE,iCAAiC;IACrE,0BAA0B,EAAE,mCAAmC,EAAE,qBAAqB;IACtF,4BAA4B,EAAE,+BAA+B,EAAE,+BAA+B;IAC9F,+BAA+B,EAAE,gCAAgC,EAAE,6BAA6B;IAChG,sCAAsC,EAAE,kCAAkC,EAAE,0BAA0B;IACtG,4CAA4C;IAC5C,qBAAqB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,sBAAsB;IACrI,6BAA6B,EAAE,gCAAgC,EAAE,6BAA6B;CACtF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ProfileSpan {
|
|
2
|
+
t: number;
|
|
3
|
+
profile: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const PROFILE_TIMELINE_FILE = "codex-profile-timeline.json";
|
|
6
|
+
export declare const PROFILE_TIMELINE_MAX = 200;
|
|
7
|
+
export declare const PROFILE_UNRECORDED = "unrecorded";
|
|
8
|
+
/** The file's entries, checked and sorted (an unreadable or missing file: none). */
|
|
9
|
+
export declare function readProfileTimeline(path: string): Promise<ProfileSpan[]>;
|
|
10
|
+
/**
|
|
11
|
+
* A launch under `profile` at `t`. One write at a time per file; nothing is added when the last entry already names the
|
|
12
|
+
* same profile. Never throws: a lost entry leaves that run's turns under the previous entry's profile (or unrecorded).
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordProfileLaunch(path: string, profile: string, t?: number): Promise<void>;
|
|
15
|
+
/** The profile in effect at `t`: the latest entry at or before it, else "unrecorded". */
|
|
16
|
+
export declare function profileAt(spans: readonly ProfileSpan[], t: number): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3066 §1d: which credential profile a Codex instance ran under, and since when — so its usage is told by account. A
|
|
3
|
+
* Codex rollout does not say which login wrote it (so its hours were all "unrecorded"); AgEnD does know which profile it
|
|
4
|
+
* launched each Codex under. Each launch appends `{ t, profile }` to the instance's `codex-profile-timeline.json`; the
|
|
5
|
+
* usage scan tags each turn with the profile in effect at its time (the latest entry at or before it). A switch of
|
|
6
|
+
* profile is a relaunch, so a turn is never between two entries of one run. Turns from before the first entry stay
|
|
7
|
+
* "unrecorded" — history is never guessed.
|
|
8
|
+
*
|
|
9
|
+
* Small (≤ 200 entries, the oldest dropped) and written off the fleet's thread, atomically.
|
|
10
|
+
*/
|
|
11
|
+
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
12
|
+
export const PROFILE_TIMELINE_FILE = "codex-profile-timeline.json";
|
|
13
|
+
export const PROFILE_TIMELINE_MAX = 200;
|
|
14
|
+
export const PROFILE_UNRECORDED = "unrecorded";
|
|
15
|
+
/** The file's entries, checked and sorted (an unreadable or missing file: none). */
|
|
16
|
+
export async function readProfileTimeline(path) {
|
|
17
|
+
let v;
|
|
18
|
+
try {
|
|
19
|
+
v = JSON.parse(await readFile(path, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const list = v && typeof v === "object" && !Array.isArray(v) && Array.isArray(v.spans) ? v.spans : [];
|
|
25
|
+
return list.filter((e) => !!e && typeof e === "object" && typeof e.t === "number"
|
|
26
|
+
&& Number.isFinite(e.t) && typeof e.profile === "string" && e.profile !== "")
|
|
27
|
+
.map((e) => ({ t: e.t, profile: e.profile })).sort((a, b) => a.t - b.t);
|
|
28
|
+
}
|
|
29
|
+
const writes = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* A launch under `profile` at `t`. One write at a time per file; nothing is added when the last entry already names the
|
|
32
|
+
* same profile. Never throws: a lost entry leaves that run's turns under the previous entry's profile (or unrecorded).
|
|
33
|
+
*/
|
|
34
|
+
export function recordProfileLaunch(path, profile, t = Date.now()) {
|
|
35
|
+
const next = (writes.get(path) ?? Promise.resolve()).then(async () => {
|
|
36
|
+
const spans = await readProfileTimeline(path);
|
|
37
|
+
if (spans.at(-1)?.profile === profile)
|
|
38
|
+
return;
|
|
39
|
+
const kept = [...spans, { t, profile }].slice(-PROFILE_TIMELINE_MAX);
|
|
40
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
41
|
+
await writeFile(tmp, JSON.stringify({ v: 1, spans: kept }), { mode: 0o600 });
|
|
42
|
+
await rename(tmp, path);
|
|
43
|
+
}).catch(() => { });
|
|
44
|
+
writes.set(path, next);
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
/** The profile in effect at `t`: the latest entry at or before it, else "unrecorded". */
|
|
48
|
+
export function profileAt(spans, t) {
|
|
49
|
+
let found = PROFILE_UNRECORDED;
|
|
50
|
+
for (const s of spans) {
|
|
51
|
+
if (s.t <= t)
|
|
52
|
+
found = s.profile;
|
|
53
|
+
else
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=profile-timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-timeline.js","sourceRoot":"","sources":["../../src/usage/profile-timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG/D,MAAM,CAAC,MAAM,qBAAqB,GAAG,6BAA6B,CAAC;AACnE,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACxC,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,IAAI,CAAU,CAAC;IACf,IAAI,CAAC;QAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAE,CAAyB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,CAA0B,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACzJ,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAiB,CAAC,CAAC,KAAK,QAAQ;WAC/G,MAAM,CAAC,QAAQ,CAAE,CAAiB,CAAC,CAAC,CAAC,IAAI,OAAQ,CAAiB,CAAC,OAAO,KAAK,QAAQ,IAAK,CAAiB,CAAC,OAAO,KAAK,EAAE,CAAC;SAC/H,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;AAChD;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,OAAe,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE;IAC/E,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;QACnE,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO;YAAE,OAAO;QAC9C,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QACzC,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7E,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAqC,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,KAA6B,EAAE,CAAS;IAChE,IAAI,KAAK,GAAG,kBAAkB,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;;YAAM,MAAM;IAAC,CAAC;IACvE,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ProviderUsage } from "./providers.js";
|
|
2
|
+
export interface QuotaReading {
|
|
3
|
+
/** When the reading was made (ms). */
|
|
4
|
+
t: number;
|
|
5
|
+
/** The usage row's id: "claude", "claude:hsinlin", "codex:yanxianchen", … — one account. */
|
|
6
|
+
provider: string;
|
|
7
|
+
/** The credential profile, when the row has one. */
|
|
8
|
+
profile?: string;
|
|
9
|
+
/** The metric's label: "Weekly", "Sonnet (weekly)", … */
|
|
10
|
+
metric: string;
|
|
11
|
+
/** "model": the vendor's per-model quota (UsageMetric.scope) — its label names the model. Absent: not one. */
|
|
12
|
+
scope?: "model";
|
|
13
|
+
/** Percent used (0–100, as the vendor reports it). */
|
|
14
|
+
used: number;
|
|
15
|
+
/** When this window resets (ISO), or null when the vendor did not say. */
|
|
16
|
+
resetsAt: string | null;
|
|
17
|
+
windowMs: number | null;
|
|
18
|
+
}
|
|
19
|
+
export declare const QUOTA_HISTORY_KEEP_MS: number;
|
|
20
|
+
export declare const QUOTA_HISTORY_MAX_BYTES: number;
|
|
21
|
+
/**
|
|
22
|
+
* The same reset (Fable r1 #5): a vendor that gives only "resets in N seconds" yields a time that drifts by the read's own
|
|
23
|
+
* timing; within 10 minutes it is the same window, never a new line on every read. A real reset moves it by days.
|
|
24
|
+
*/
|
|
25
|
+
export declare const RESET_TOLERANCE_MS: number;
|
|
26
|
+
export declare function sameReset(a: string | null, b: string | null): boolean;
|
|
27
|
+
/** A line from the file, checked whole (it is not trusted): null when it is not a reading. */
|
|
28
|
+
export declare function parseQuotaReading(line: string): QuotaReading | null;
|
|
29
|
+
/** The weekly-or-longer percent metrics of a good read, as readings at `now`. */
|
|
30
|
+
export declare function quotaReadingsOf(p: ProviderUsage, now: number): QuotaReading[];
|
|
31
|
+
/**
|
|
32
|
+
* Record a good read's weekly readings: a line for each metric whose percent or reset time changed since its last line.
|
|
33
|
+
* Never throws and never blocks the caller: the write is queued (one at a time) and a failure only loses this reading.
|
|
34
|
+
*/
|
|
35
|
+
export declare function recordQuotaReadings(p: ProviderUsage, now?: number): void;
|
|
36
|
+
/** Every reading kept, oldest first (§1 reads it). Unreadable lines are skipped. */
|
|
37
|
+
export declare function readQuotaHistory(): Promise<QuotaReading[]>;
|
|
38
|
+
/** Tests: write to `path` (null: nowhere), starting from what that file holds; resolves when queued writes are done. */
|
|
39
|
+
export declare function setQuotaHistoryFileForTests(path: string | null): Promise<void>;
|
|
40
|
+
/** Tests (usage-api's fetcher seam): record nothing, at once — never into the real AGEND_HOME. */
|
|
41
|
+
export declare function quotaHistoryOffForTests(): void;
|
|
42
|
+
/** Tests: wait for the queued writes. */
|
|
43
|
+
export declare function flushQuotaHistory(): Promise<void>;
|