sidekick-shared 0.24.5 → 0.26.0
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/CHANGELOG.md +92 -0
- package/README.md +205 -52
- package/dist/accountChangeSignal.d.ts +3 -0
- package/dist/accountChangeSignal.js +19 -0
- package/dist/accountChanges.d.ts +21 -0
- package/dist/accountChanges.js +170 -0
- package/dist/accountRegistry.d.ts +25 -2
- package/dist/accountRegistry.js +41 -13
- package/dist/accountStatus.d.ts +9 -1
- package/dist/accountStatus.js +3 -3
- package/dist/accounts.d.ts +3 -1
- package/dist/accounts.js +84 -64
- package/dist/aggregation/EventAggregator.d.ts +15 -2
- package/dist/aggregation/EventAggregator.js +54 -9
- package/dist/aggregation/costProvenance.d.ts +14 -0
- package/dist/aggregation/costProvenance.js +35 -0
- package/dist/aggregation/snapshot.d.ts +13 -2
- package/dist/aggregation/snapshot.js +57 -6
- package/dist/aggregation/types.d.ts +30 -1
- package/dist/analytics/compactionLedger.js +15 -5
- package/dist/analytics/qualityScore.js +2 -1
- package/dist/browser.d.ts +19 -4
- package/dist/browser.js +43 -1
- package/dist/codexProfiles.d.ts +3 -1
- package/dist/codexProfiles.js +4 -2
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +11 -66
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/failingTools.d.ts +37 -0
- package/dist/failingTools.js +70 -0
- package/dist/formatters/sessionDump.js +13 -7
- package/dist/formatting.d.ts +13 -0
- package/dist/formatting.js +35 -0
- package/dist/historicalStore.d.ts +47 -0
- package/dist/historicalStore.js +312 -0
- package/dist/index.d.ts +67 -23
- package/dist/index.js +138 -26
- package/dist/modelAliases.d.ts +13 -0
- package/dist/modelAliases.js +58 -0
- package/dist/modelCatalog.d.ts +32 -0
- package/dist/modelCatalog.js +145 -0
- package/dist/modelContext.d.ts +36 -3
- package/dist/modelContext.js +109 -34
- package/dist/modelInfo.d.ts +30 -1
- package/dist/modelInfo.js +116 -26
- package/dist/multiProviderQuotaService.d.ts +8 -0
- package/dist/multiProviderQuotaService.js +64 -3
- package/dist/node.d.ts +23 -4
- package/dist/node.js +50 -1
- package/dist/observedContextWindows.d.ts +7 -0
- package/dist/observedContextWindows.js +17 -2
- package/dist/observedSessionCollector.d.ts +72 -6
- package/dist/observedSessionCollector.js +440 -47
- package/dist/parsers/codexHistory.d.ts +4 -2
- package/dist/parsers/codexHistory.js +9 -2
- package/dist/parsers/sessionActivityDetector.d.ts +28 -0
- package/dist/parsers/sessionActivityDetector.js +110 -15
- package/dist/parsers/sessionPathResolver.d.ts +11 -0
- package/dist/parsers/sessionPathResolver.js +13 -10
- package/dist/peakHours.d.ts +34 -3
- package/dist/peakHours.js +92 -7
- package/dist/pricingCatalog.d.ts +7 -0
- package/dist/pricingCatalog.js +8 -5
- package/dist/providerIds.d.ts +11 -0
- package/dist/providerIds.js +9 -0
- package/dist/providerQuota.d.ts +3 -1
- package/dist/providerQuota.js +3 -0
- package/dist/providers/claudeCode.d.ts +10 -1
- package/dist/providers/claudeCode.js +211 -131
- package/dist/providers/codex.d.ts +20 -1
- package/dist/providers/codex.js +308 -244
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/detect.d.ts +2 -0
- package/dist/providers/detect.js +29 -24
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +20 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +22 -1
- package/dist/providers/openCode.js +460 -126
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/rolloutWalker.d.ts +53 -0
- package/dist/providers/rolloutWalker.js +209 -0
- package/dist/providers/types.d.ts +82 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +34 -4
- package/dist/quota.js +37 -9
- package/dist/quotaHistory.d.ts +3 -2
- package/dist/quotaHistory.js +36 -35
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaResolver.d.ts +81 -0
- package/dist/quotaResolver.js +213 -0
- package/dist/quotaSnapshots.d.ts +15 -2
- package/dist/quotaSnapshots.js +25 -3
- package/dist/quotaThresholds.d.ts +60 -0
- package/dist/quotaThresholds.js +90 -0
- package/dist/report/htmlReportGenerator.js +10 -4
- package/dist/report/sessionReportInputs.d.ts +23 -0
- package/dist/report/sessionReportInputs.js +34 -0
- package/dist/schemas/accountManager.d.ts +2 -2
- package/dist/schemas/accountManager.js +2 -4
- package/dist/schemas/accountStatus.d.ts +3 -0
- package/dist/schemas/accountStatus.js +1 -0
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.js +16 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +141 -2
- package/dist/schemas/quota.js +20 -8
- package/dist/schemas/quotaHistory.d.ts +3 -2
- package/dist/schemas/quotaHistory.js +1 -1
- package/dist/schemas/sessionEvent.d.ts +6 -0
- package/dist/schemas/sessionEvent.js +1 -0
- package/dist/schemas/stateFile.d.ts +198 -0
- package/dist/schemas/stateFile.js +77 -0
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/schemas/transcript.js +2 -0
- package/dist/sessionFingerprint.d.ts +25 -0
- package/dist/sessionFingerprint.js +46 -0
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +33 -2
- package/dist/sessionPreviews.js +333 -6
- package/dist/sessionStats.d.ts +51 -0
- package/dist/sessionStats.js +166 -0
- package/dist/stateFile.d.ts +113 -0
- package/dist/stateFile.js +160 -0
- package/dist/statusline/claudeStatuslinePayload.d.ts +89 -0
- package/dist/statusline/claudeStatuslinePayload.js +152 -0
- package/dist/statusline/formatter.d.ts +13 -0
- package/dist/statusline/formatter.js +48 -6
- package/dist/statusline/index.d.ts +10 -2
- package/dist/statusline/index.js +19 -1
- package/dist/tokenSummary.d.ts +62 -0
- package/dist/tokenSummary.js +72 -0
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.js +2 -0
- package/dist/types/observedSessionV1.d.ts +8 -2
- package/dist/types/observedSessionV1.js +88 -25
- package/dist/types/sessionEvent.d.ts +2 -0
- package/dist/usage/billingBlocks.d.ts +85 -0
- package/dist/usage/billingBlocks.js +153 -0
- package/dist/usage/importSessionHistory.d.ts +53 -0
- package/dist/usage/importSessionHistory.js +88 -0
- package/dist/usage/usageEvents.d.ts +92 -0
- package/dist/usage/usageEvents.js +322 -0
- package/dist/usage/usageReports.d.ts +76 -0
- package/dist/usage/usageReports.js +208 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +30 -25
- package/dist/zaiQuotaApi.d.ts +2 -0
- package/dist/zaiQuotaApi.js +2 -0
- package/package.json +22 -1
package/dist/codexQuota.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.enrichCodexQuota = enrichCodexQuota;
|
|
36
37
|
exports.quotaFromCodexRateLimits = quotaFromCodexRateLimits;
|
|
37
38
|
exports.readLatestCodexQuotaFromRollouts = readLatestCodexQuotaFromRollouts;
|
|
38
39
|
exports.resolveCodexQuotaFromLocalSources = resolveCodexQuotaFromLocalSources;
|
|
@@ -45,6 +46,7 @@ const codexProfiles_1 = require("./codexProfiles");
|
|
|
45
46
|
const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
46
47
|
const quota_1 = require("./quota");
|
|
47
48
|
const codex_1 = require("./providers/codex");
|
|
49
|
+
const rolloutWalker_1 = require("./providers/rolloutWalker");
|
|
48
50
|
const codex_2 = require("./types/codex");
|
|
49
51
|
const DEFAULT_TAIL_BYTES = 2 * 1024 * 1024;
|
|
50
52
|
const DEFAULT_MAX_SESSION_FILES = 50;
|
|
@@ -105,6 +107,7 @@ function timestampToIso(seconds) {
|
|
|
105
107
|
function accountEmail(account) {
|
|
106
108
|
return account?.email ?? account?.metadata?.email;
|
|
107
109
|
}
|
|
110
|
+
/** Stamp a Codex quota sample with its runtime provider, projections, and account identity. */
|
|
108
111
|
function enrichCodexQuota(state, account) {
|
|
109
112
|
const withProjections = (0, quota_1.withQuotaProjections)(state);
|
|
110
113
|
return {
|
|
@@ -278,39 +281,14 @@ function readLatestCodexQuotaHitFromRollouts(sessionPaths, options = {}) {
|
|
|
278
281
|
}
|
|
279
282
|
return latest;
|
|
280
283
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
seen.add(filePath);
|
|
288
|
-
unique.push(filePath);
|
|
289
|
-
}
|
|
290
|
-
return unique;
|
|
291
|
-
}
|
|
292
|
-
function sortPathsByMtimeDesc(paths) {
|
|
293
|
-
return [...paths].sort((a, b) => {
|
|
294
|
-
const aMtime = safeMtimeMs(a);
|
|
295
|
-
const bMtime = safeMtimeMs(b);
|
|
296
|
-
return bMtime - aMtime;
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
function safeMtimeMs(filePath) {
|
|
300
|
-
try {
|
|
301
|
-
return fs.statSync(filePath).mtime.getTime();
|
|
302
|
-
}
|
|
303
|
-
catch {
|
|
304
|
-
return 0;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function findAccountRolloutFiles(codexHome) {
|
|
284
|
+
/**
|
|
285
|
+
* The newest account-level rollouts, one capped walk across the monitored
|
|
286
|
+
* homes. `limit` bounds the walk itself, so a large history is never fully
|
|
287
|
+
* enumerated or stat'ed just to tail a handful of files.
|
|
288
|
+
*/
|
|
289
|
+
function findAccountRolloutFiles(codexHome, limit) {
|
|
308
290
|
const homes = codexHome ? [codexHome] : (0, codexProfiles_1.getCodexMonitoringHomes)();
|
|
309
|
-
|
|
310
|
-
for (const home of homes) {
|
|
311
|
-
files.push(...findRolloutFiles(path.join(home, 'sessions')));
|
|
312
|
-
}
|
|
313
|
-
return sortPathsByMtimeDesc(dedupePaths(files));
|
|
291
|
+
return (0, rolloutWalker_1.walkRolloutFiles)(homes.map((home) => path.join(home, 'sessions')), { limit }).map((file) => file.path);
|
|
314
292
|
}
|
|
315
293
|
function resolveCodexQuotaFromLocalSources(options = {}) {
|
|
316
294
|
const account = options.activeAccount !== undefined ? options.activeAccount : (0, codexProfiles_1.getActiveCodexAccount)();
|
|
@@ -335,7 +313,7 @@ function resolveCodexQuotaFromLocalSources(options = {}) {
|
|
|
335
313
|
if (workspaceHit)
|
|
336
314
|
candidates.push(workspaceHit);
|
|
337
315
|
}
|
|
338
|
-
const accountSessions = findAccountRolloutFiles(options.codexHome);
|
|
316
|
+
const accountSessions = findAccountRolloutFiles(options.codexHome, maxSessionFiles);
|
|
339
317
|
const accountHit = readLatestCodexQuotaHitFromRollouts(accountSessions, {
|
|
340
318
|
maxTailBytes,
|
|
341
319
|
maxSessionFiles,
|
|
@@ -626,36 +604,3 @@ function readLatestQuotaFromRollout(sessionPath, maxTailBytes, source) {
|
|
|
626
604
|
}
|
|
627
605
|
return null;
|
|
628
606
|
}
|
|
629
|
-
function findRolloutFiles(sessionsDir) {
|
|
630
|
-
const results = [];
|
|
631
|
-
function visit(dir) {
|
|
632
|
-
let entries;
|
|
633
|
-
try {
|
|
634
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
635
|
-
}
|
|
636
|
-
catch {
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
for (const entry of entries) {
|
|
640
|
-
const fullPath = path.join(dir, entry.name);
|
|
641
|
-
if (entry.isDirectory()) {
|
|
642
|
-
visit(fullPath);
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (!entry.isFile() || !entry.name.startsWith('rollout-') || !entry.name.endsWith('.jsonl'))
|
|
646
|
-
continue;
|
|
647
|
-
try {
|
|
648
|
-
const stat = fs.statSync(fullPath);
|
|
649
|
-
if (stat.size > 0) {
|
|
650
|
-
results.push({ path: fullPath, mtime: stat.mtime.getTime() });
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
// Skip inaccessible files.
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
visit(sessionsDir);
|
|
659
|
-
results.sort((a, b) => b.mtime - a.mtime);
|
|
660
|
-
return results.map((item) => item.path);
|
|
661
|
-
}
|
|
@@ -6,6 +6,7 @@ import type { SavedAccountProfile } from './accountRegistry';
|
|
|
6
6
|
import type { Disposable } from './quotaPoller';
|
|
7
7
|
import type { QuotaState } from './quota';
|
|
8
8
|
import type { ProviderQuotaState } from './providerQuota';
|
|
9
|
+
import { type AccountsChangedEvent } from './accountChanges';
|
|
9
10
|
type CodexAccountReader = () => SavedAccountProfile | null;
|
|
10
11
|
type SnapshotReader = (providerId: 'codex', accountId: string) => QuotaState | null;
|
|
11
12
|
type SnapshotWriter = (providerId: 'codex', accountId: string, quota: QuotaState) => void;
|
|
@@ -27,6 +28,7 @@ export interface CodexQuotaWatcherOptions {
|
|
|
27
28
|
workspaceId?: string;
|
|
28
29
|
/** Override the history append function (used by tests). Default: `appendQuotaHistorySample`. */
|
|
29
30
|
appendHistorySample?: HistoryAppender;
|
|
31
|
+
subscribeAccountsChanged?: (listener: (event: AccountsChangedEvent) => void) => Disposable;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Watches the active Codex rollout for quota snapshots and falls back to the
|
|
@@ -46,6 +48,7 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
46
48
|
private readonly now;
|
|
47
49
|
private readonly workspaceId;
|
|
48
50
|
private readonly appendHistorySample;
|
|
51
|
+
private readonly subscribeAccountsChanged;
|
|
49
52
|
private readonly listeners;
|
|
50
53
|
private discoveryTimer;
|
|
51
54
|
private provider;
|
|
@@ -56,6 +59,7 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
56
59
|
private lastLocalScanAt;
|
|
57
60
|
private lastLocalScanState;
|
|
58
61
|
private running;
|
|
62
|
+
private accountSubscription;
|
|
59
63
|
constructor(workspacePath: string, options?: CodexQuotaWatcherOptions);
|
|
60
64
|
start(): void;
|
|
61
65
|
stop(): void;
|
|
@@ -63,12 +67,14 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
63
67
|
onUpdate(cb: (state: ProviderQuotaState<'codex'>) => void): Disposable;
|
|
64
68
|
onQuotaUpdate(cb: (state: ProviderQuotaState<'codex'>) => void): Disposable;
|
|
65
69
|
refresh(): void;
|
|
70
|
+
private handleAccountsChanged;
|
|
66
71
|
private refreshActiveSession;
|
|
67
72
|
private attachToSession;
|
|
68
73
|
private ingestSessionUpdate;
|
|
69
74
|
private emitCachedOrUnavailable;
|
|
70
75
|
private emitState;
|
|
71
76
|
private getProvider;
|
|
77
|
+
private safeGetActiveAccount;
|
|
72
78
|
private teardownActiveSession;
|
|
73
79
|
}
|
|
74
80
|
export {};
|
|
@@ -40,6 +40,7 @@ const codexQuota_1 = require("./codexQuota");
|
|
|
40
40
|
const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
41
41
|
const quotaHistory_1 = require("./quotaHistory");
|
|
42
42
|
const codex_1 = require("./providers/codex");
|
|
43
|
+
const accountChanges_1 = require("./accountChanges");
|
|
43
44
|
const DEFAULT_DISCOVERY_POLL_INTERVAL_MS = 30_000;
|
|
44
45
|
const DEFAULT_LOCAL_SCAN_CACHE_MS = 5 * 60_000;
|
|
45
46
|
function accountEmail(account) {
|
|
@@ -86,6 +87,7 @@ class CodexQuotaWatcher {
|
|
|
86
87
|
now;
|
|
87
88
|
workspaceId;
|
|
88
89
|
appendHistorySample;
|
|
90
|
+
subscribeAccountsChanged;
|
|
89
91
|
listeners = [];
|
|
90
92
|
discoveryTimer;
|
|
91
93
|
provider = null;
|
|
@@ -96,6 +98,7 @@ class CodexQuotaWatcher {
|
|
|
96
98
|
lastLocalScanAt = Number.NEGATIVE_INFINITY;
|
|
97
99
|
lastLocalScanState = null;
|
|
98
100
|
running = false;
|
|
101
|
+
accountSubscription = null;
|
|
99
102
|
constructor(workspacePath, options = {}) {
|
|
100
103
|
this.workspacePath = workspacePath;
|
|
101
104
|
this.discoveryPollIntervalMs =
|
|
@@ -119,15 +122,14 @@ class CodexQuotaWatcher {
|
|
|
119
122
|
this.now = options.now ?? Date.now;
|
|
120
123
|
this.workspaceId = options.workspaceId;
|
|
121
124
|
this.appendHistorySample = options.appendHistorySample ?? quotaHistory_1.appendQuotaHistorySample;
|
|
125
|
+
this.subscribeAccountsChanged = options.subscribeAccountsChanged ?? accountChanges_1.onAccountsChanged;
|
|
122
126
|
}
|
|
123
127
|
start() {
|
|
124
128
|
if (this.running)
|
|
125
129
|
return;
|
|
126
130
|
this.running = true;
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
129
|
-
this.refreshActiveSession();
|
|
130
|
-
}, this.discoveryPollIntervalMs);
|
|
131
|
+
this.accountSubscription ??= this.subscribeAccountsChanged(() => this.handleAccountsChanged());
|
|
132
|
+
this.handleAccountsChanged();
|
|
131
133
|
}
|
|
132
134
|
stop() {
|
|
133
135
|
if (!this.running)
|
|
@@ -137,6 +139,8 @@ class CodexQuotaWatcher {
|
|
|
137
139
|
clearInterval(this.discoveryTimer);
|
|
138
140
|
this.discoveryTimer = undefined;
|
|
139
141
|
}
|
|
142
|
+
this.accountSubscription?.dispose();
|
|
143
|
+
this.accountSubscription = null;
|
|
140
144
|
this.teardownActiveSession();
|
|
141
145
|
}
|
|
142
146
|
dispose() {
|
|
@@ -157,21 +161,46 @@ class CodexQuotaWatcher {
|
|
|
157
161
|
return this.onUpdate(cb);
|
|
158
162
|
}
|
|
159
163
|
refresh() {
|
|
160
|
-
this.
|
|
164
|
+
this.handleAccountsChanged();
|
|
161
165
|
}
|
|
162
|
-
|
|
166
|
+
handleAccountsChanged() {
|
|
167
|
+
if (!this.running)
|
|
168
|
+
return;
|
|
169
|
+
const account = this.safeGetActiveAccount();
|
|
170
|
+
if (!account) {
|
|
171
|
+
if (this.discoveryTimer) {
|
|
172
|
+
clearInterval(this.discoveryTimer);
|
|
173
|
+
this.discoveryTimer = undefined;
|
|
174
|
+
}
|
|
175
|
+
this.teardownActiveSession();
|
|
176
|
+
this.emitState(makeUnavailableState(null, 'No Codex account configured'));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!this.discoveryTimer) {
|
|
180
|
+
this.discoveryTimer = setInterval(() => {
|
|
181
|
+
this.refreshActiveSession();
|
|
182
|
+
}, this.discoveryPollIntervalMs);
|
|
183
|
+
this.discoveryTimer.unref?.();
|
|
184
|
+
}
|
|
185
|
+
this.refreshActiveSession(account);
|
|
186
|
+
}
|
|
187
|
+
refreshActiveSession(account = this.safeGetActiveAccount()) {
|
|
188
|
+
if (!account) {
|
|
189
|
+
this.handleAccountsChanged();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
163
192
|
const provider = this.getProvider();
|
|
164
193
|
const nextSessionPath = provider.findActiveSession(this.workspacePath);
|
|
165
194
|
if (!nextSessionPath) {
|
|
166
195
|
this.teardownActiveSession();
|
|
167
|
-
this.emitCachedOrUnavailable();
|
|
196
|
+
this.emitCachedOrUnavailable(account);
|
|
168
197
|
return;
|
|
169
198
|
}
|
|
170
199
|
if (nextSessionPath !== this.sessionPath || this.reader == null) {
|
|
171
200
|
this.attachToSession(nextSessionPath);
|
|
172
201
|
return;
|
|
173
202
|
}
|
|
174
|
-
this.ingestSessionUpdate('readNew');
|
|
203
|
+
this.ingestSessionUpdate('readNew', account);
|
|
175
204
|
}
|
|
176
205
|
attachToSession(nextSessionPath) {
|
|
177
206
|
this.teardownActiveSession();
|
|
@@ -189,14 +218,30 @@ class CodexQuotaWatcher {
|
|
|
189
218
|
}
|
|
190
219
|
this.refreshActiveSession();
|
|
191
220
|
});
|
|
221
|
+
if (typeof this.fileWatcher.on === 'function') {
|
|
222
|
+
this.fileWatcher.on('error', () => {
|
|
223
|
+
try {
|
|
224
|
+
this.fileWatcher?.close();
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Discovery polling remains active.
|
|
228
|
+
}
|
|
229
|
+
this.fileWatcher = null;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
192
232
|
}
|
|
193
233
|
catch {
|
|
194
234
|
this.emitCachedOrUnavailable();
|
|
195
235
|
}
|
|
196
236
|
}
|
|
197
|
-
ingestSessionUpdate(mode) {
|
|
237
|
+
ingestSessionUpdate(mode, knownAccount) {
|
|
238
|
+
const account = knownAccount ?? this.safeGetActiveAccount();
|
|
239
|
+
if (!account) {
|
|
240
|
+
this.handleAccountsChanged();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
198
243
|
if (!this.provider || !this.reader) {
|
|
199
|
-
this.emitCachedOrUnavailable();
|
|
244
|
+
this.emitCachedOrUnavailable(account);
|
|
200
245
|
return;
|
|
201
246
|
}
|
|
202
247
|
if (!this.reader.exists()) {
|
|
@@ -211,48 +256,45 @@ class CodexQuotaWatcher {
|
|
|
211
256
|
}
|
|
212
257
|
const liveQuota = (0, codexQuota_1.quotaFromCodexRateLimits)(this.provider.getLastRateLimits(), 'session');
|
|
213
258
|
if (!liveQuota) {
|
|
214
|
-
this.emitCachedOrUnavailable();
|
|
259
|
+
this.emitCachedOrUnavailable(account);
|
|
215
260
|
return;
|
|
216
261
|
}
|
|
217
|
-
const
|
|
218
|
-
const cached = account ? this.readSnapshot('codex', account.id) : null;
|
|
262
|
+
const cached = this.readSnapshot('codex', account.id);
|
|
219
263
|
const liveQuotaWithResetCredits = {
|
|
220
264
|
...liveQuota,
|
|
221
265
|
resetCredits: liveQuota.resetCredits ?? cached?.resetCredits,
|
|
222
266
|
};
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
// Synchronous errors swallowed for the same reason.
|
|
267
|
+
this.writeSnapshot('codex', account.id, liveQuotaWithResetCredits);
|
|
268
|
+
if (this.workspaceId) {
|
|
269
|
+
const sample = {
|
|
270
|
+
timestamp: liveQuotaWithResetCredits.capturedAt ?? new Date().toISOString(),
|
|
271
|
+
runtimeProvider: 'codex',
|
|
272
|
+
providerId: account.id,
|
|
273
|
+
workspaceId: this.workspaceId,
|
|
274
|
+
fiveHour: {
|
|
275
|
+
utilization: liveQuotaWithResetCredits.fiveHour.utilization,
|
|
276
|
+
resetsAt: liveQuotaWithResetCredits.fiveHour.resetsAt,
|
|
277
|
+
},
|
|
278
|
+
sevenDay: {
|
|
279
|
+
utilization: liveQuotaWithResetCredits.sevenDay.utilization,
|
|
280
|
+
resetsAt: liveQuotaWithResetCredits.sevenDay.resetsAt,
|
|
281
|
+
},
|
|
282
|
+
available: liveQuotaWithResetCredits.available,
|
|
283
|
+
error: liveQuotaWithResetCredits.error,
|
|
284
|
+
source: liveQuotaWithResetCredits.source,
|
|
285
|
+
stale: liveQuotaWithResetCredits.stale,
|
|
286
|
+
};
|
|
287
|
+
try {
|
|
288
|
+
const result = this.appendHistorySample(sample);
|
|
289
|
+
if (result && typeof result.catch === 'function') {
|
|
290
|
+
result.catch(() => {
|
|
291
|
+
// History append must never break the live emission path.
|
|
292
|
+
});
|
|
254
293
|
}
|
|
255
294
|
}
|
|
295
|
+
catch {
|
|
296
|
+
// Synchronous errors swallowed for the same reason.
|
|
297
|
+
}
|
|
256
298
|
}
|
|
257
299
|
this.emitState(enrichQuotaState({
|
|
258
300
|
...liveQuotaWithResetCredits,
|
|
@@ -260,8 +302,12 @@ class CodexQuotaWatcher {
|
|
|
260
302
|
providerId: 'codex',
|
|
261
303
|
}, account));
|
|
262
304
|
}
|
|
263
|
-
emitCachedOrUnavailable() {
|
|
264
|
-
const account = this.
|
|
305
|
+
emitCachedOrUnavailable(knownAccount) {
|
|
306
|
+
const account = knownAccount ?? this.safeGetActiveAccount();
|
|
307
|
+
if (!account) {
|
|
308
|
+
this.emitState(makeUnavailableState(null, 'No Codex account configured'));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
265
311
|
const scanNow = this.now();
|
|
266
312
|
if (scanNow - this.lastLocalScanAt < this.localScanCacheMs) {
|
|
267
313
|
if (this.lastLocalScanState) {
|
|
@@ -296,7 +342,7 @@ class CodexQuotaWatcher {
|
|
|
296
342
|
localProvider?.dispose();
|
|
297
343
|
}
|
|
298
344
|
}
|
|
299
|
-
const cached =
|
|
345
|
+
const cached = this.readSnapshot('codex', account.id);
|
|
300
346
|
if (cached) {
|
|
301
347
|
this.emitState(enrichQuotaState({
|
|
302
348
|
...cached,
|
|
@@ -331,6 +377,14 @@ class CodexQuotaWatcher {
|
|
|
331
377
|
}
|
|
332
378
|
return this.provider;
|
|
333
379
|
}
|
|
380
|
+
safeGetActiveAccount() {
|
|
381
|
+
try {
|
|
382
|
+
return this.getActiveAccount();
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
334
388
|
teardownActiveSession() {
|
|
335
389
|
if (this.fileWatcher) {
|
|
336
390
|
this.fileWatcher.close();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failing-tool trend rows shared by the CLI `stats` block and the VS Code
|
|
3
|
+
* Health tab. Browser-safe: no filesystem access; callers pass the two
|
|
4
|
+
* windows they read with `getTopFailingTools()`.
|
|
5
|
+
*
|
|
6
|
+
* @module failingTools
|
|
7
|
+
*/
|
|
8
|
+
/** The shape `getTopFailingTools()` returns; declared structurally to stay fs-free. */
|
|
9
|
+
export interface FailingToolWindow {
|
|
10
|
+
tool: string;
|
|
11
|
+
failures: number;
|
|
12
|
+
categories?: Record<string, number>;
|
|
13
|
+
}
|
|
14
|
+
export type FailingToolTrend = 'up' | 'down' | 'flat';
|
|
15
|
+
export interface FailingToolTrendRow {
|
|
16
|
+
tool: string;
|
|
17
|
+
/** Failures in the last 7 days. */
|
|
18
|
+
last7: number;
|
|
19
|
+
/** Failures in the last 30 days. */
|
|
20
|
+
last30: number;
|
|
21
|
+
/** Category counts over the 30-day window (or the 7-day one when only that is known). */
|
|
22
|
+
categories: Record<string, number>;
|
|
23
|
+
/** Whether the last week is worse, better, or in line with the 30-day weekly average. */
|
|
24
|
+
trend: FailingToolTrend;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compare the last week against the 30-day weekly average: more than 10%
|
|
28
|
+
* above is `up`, more than 10% below is `down`, otherwise `flat`. A tool
|
|
29
|
+
* with no 30-day history but failures this week is `up`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function failingToolTrend(last7: number, last30: number): FailingToolTrend;
|
|
32
|
+
export declare const FAILING_TOOL_TREND_ARROWS: Record<FailingToolTrend, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Merge the 7-day and 30-day windows into one row per tool, ordered by
|
|
35
|
+
* 7-day failures, then 30-day failures, then name.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mergeFailingToolWindows(last7: FailingToolWindow[], last30: FailingToolWindow[]): FailingToolTrendRow[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Failing-tool trend rows shared by the CLI `stats` block and the VS Code
|
|
4
|
+
* Health tab. Browser-safe: no filesystem access; callers pass the two
|
|
5
|
+
* windows they read with `getTopFailingTools()`.
|
|
6
|
+
*
|
|
7
|
+
* @module failingTools
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.FAILING_TOOL_TREND_ARROWS = void 0;
|
|
11
|
+
exports.failingToolTrend = failingToolTrend;
|
|
12
|
+
exports.mergeFailingToolWindows = mergeFailingToolWindows;
|
|
13
|
+
const TREND_TOLERANCE = 0.1;
|
|
14
|
+
/**
|
|
15
|
+
* Compare the last week against the 30-day weekly average: more than 10%
|
|
16
|
+
* above is `up`, more than 10% below is `down`, otherwise `flat`. A tool
|
|
17
|
+
* with no 30-day history but failures this week is `up`.
|
|
18
|
+
*/
|
|
19
|
+
function failingToolTrend(last7, last30) {
|
|
20
|
+
const weeklyAverage = last30 / (30 / 7);
|
|
21
|
+
if (weeklyAverage <= 0)
|
|
22
|
+
return last7 > 0 ? 'up' : 'flat';
|
|
23
|
+
const ratio = last7 / weeklyAverage;
|
|
24
|
+
if (ratio > 1 + TREND_TOLERANCE)
|
|
25
|
+
return 'up';
|
|
26
|
+
if (ratio < 1 - TREND_TOLERANCE)
|
|
27
|
+
return 'down';
|
|
28
|
+
return 'flat';
|
|
29
|
+
}
|
|
30
|
+
exports.FAILING_TOOL_TREND_ARROWS = {
|
|
31
|
+
up: '↑',
|
|
32
|
+
down: '↓',
|
|
33
|
+
flat: '→',
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Merge the 7-day and 30-day windows into one row per tool, ordered by
|
|
37
|
+
* 7-day failures, then 30-day failures, then name.
|
|
38
|
+
*/
|
|
39
|
+
function mergeFailingToolWindows(last7, last30) {
|
|
40
|
+
const rows = new Map();
|
|
41
|
+
for (const tool of last30) {
|
|
42
|
+
rows.set(tool.tool, {
|
|
43
|
+
tool: tool.tool,
|
|
44
|
+
last7: 0,
|
|
45
|
+
last30: tool.failures,
|
|
46
|
+
categories: { ...(tool.categories ?? {}) },
|
|
47
|
+
trend: 'flat',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
for (const tool of last7) {
|
|
51
|
+
const row = rows.get(tool.tool);
|
|
52
|
+
if (row) {
|
|
53
|
+
row.last7 = tool.failures;
|
|
54
|
+
// A 7-day count cannot exceed the 30-day one; tolerate windows read at different instants.
|
|
55
|
+
row.last30 = Math.max(row.last30, tool.failures);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
rows.set(tool.tool, {
|
|
59
|
+
tool: tool.tool,
|
|
60
|
+
last7: tool.failures,
|
|
61
|
+
last30: tool.failures,
|
|
62
|
+
categories: { ...(tool.categories ?? {}) },
|
|
63
|
+
trend: 'flat',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const row of rows.values())
|
|
68
|
+
row.trend = failingToolTrend(row.last7, row.last30);
|
|
69
|
+
return [...rows.values()].sort((a, b) => b.last7 - a.last7 || b.last30 - a.last30 || a.tool.localeCompare(b.tool));
|
|
70
|
+
}
|
|
@@ -15,7 +15,9 @@ exports.fmtTokens = fmtTokens;
|
|
|
15
15
|
exports.fmtCost = fmtCost;
|
|
16
16
|
exports.formatTimestamp = formatTimestamp;
|
|
17
17
|
exports.formatDuration = formatDuration;
|
|
18
|
+
const costProvenance_1 = require("../aggregation/costProvenance");
|
|
18
19
|
const formatting_1 = require("../formatting");
|
|
20
|
+
const tokenSummary_1 = require("../tokenSummary");
|
|
19
21
|
// ── JSON output ──
|
|
20
22
|
/**
|
|
21
23
|
* Serialize aggregated metrics as pretty-printed JSON.
|
|
@@ -128,9 +130,12 @@ function formatSessionMarkdown(metrics, options = {}) {
|
|
|
128
130
|
lines.push(`| Output | ${fmtTokens(metrics.tokens.outputTokens)} |`);
|
|
129
131
|
lines.push(`| Cache write | ${fmtTokens(metrics.tokens.cacheWriteTokens)} |`);
|
|
130
132
|
lines.push(`| Cache read | ${fmtTokens(metrics.tokens.cacheReadTokens)} |`);
|
|
131
|
-
lines.push(`|
|
|
132
|
-
if (metrics.tokens.
|
|
133
|
-
lines.push(`| Cost | ${fmtCost(metrics.tokens.
|
|
133
|
+
lines.push(`| ${tokenSummary_1.TOKEN_TOTAL_LABEL} | ${fmtTokens((0, tokenSummary_1.summarizeTokens)(metrics.tokens).total)} |`);
|
|
134
|
+
if (metrics.tokens.costUsd > 0) {
|
|
135
|
+
lines.push(`| Cost | ${fmtCost(metrics.tokens.costUsd)} (${(0, costProvenance_1.describeCostProvenance)(metrics.tokens)}) |`);
|
|
136
|
+
}
|
|
137
|
+
else if (metrics.tokens.unpricedCalls > 0) {
|
|
138
|
+
lines.push(`| Cost | — (${(0, costProvenance_1.describeCostProvenance)(metrics.tokens)}) |`);
|
|
134
139
|
}
|
|
135
140
|
lines.push('');
|
|
136
141
|
// Model stats
|
|
@@ -222,9 +227,9 @@ function formatHeader(metrics) {
|
|
|
222
227
|
}
|
|
223
228
|
function formatTokenSummary(metrics) {
|
|
224
229
|
const t = metrics.tokens;
|
|
225
|
-
const total =
|
|
230
|
+
const total = (0, tokenSummary_1.summarizeTokens)(t).total;
|
|
226
231
|
const parts = [
|
|
227
|
-
`Tokens: ${fmtTokens(total)} total`,
|
|
232
|
+
`Tokens: ${fmtTokens(total)} total incl. cache`,
|
|
228
233
|
`(${fmtTokens(t.inputTokens)} in`,
|
|
229
234
|
`${fmtTokens(t.outputTokens)} out`,
|
|
230
235
|
];
|
|
@@ -233,8 +238,9 @@ function formatTokenSummary(metrics) {
|
|
|
233
238
|
if (t.cacheWriteTokens > 0)
|
|
234
239
|
parts.push(`${fmtTokens(t.cacheWriteTokens)} cache-write`);
|
|
235
240
|
const line = parts.join(', ');
|
|
236
|
-
if (t.
|
|
237
|
-
return `${line}) Cost: ${fmtCost(t.
|
|
241
|
+
if (t.costUsd > 0) {
|
|
242
|
+
return `${line}) Cost: ${fmtCost(t.costUsd)} (${(0, costProvenance_1.describeCostProvenance)(t)})`;
|
|
243
|
+
}
|
|
238
244
|
return line + ')';
|
|
239
245
|
}
|
|
240
246
|
/** @internal Exported for tests. */
|
package/dist/formatting.d.ts
CHANGED
|
@@ -24,3 +24,16 @@ export interface FormatDurationMsOptions {
|
|
|
24
24
|
export declare function formatTokenCount(value: number, options?: FormatTokenCountOptions): string;
|
|
25
25
|
/** Format an elapsed duration in milliseconds. */
|
|
26
26
|
export declare function formatDurationMs(ms: number, options?: FormatDurationMsOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* Calendar-date key (`YYYY-MM-DD`) in the machine's local time zone.
|
|
29
|
+
*
|
|
30
|
+
* Sidekick buckets "today", daily history, and heatmaps by the user's local
|
|
31
|
+
* day, so every writer and reader of a date key goes through this helper.
|
|
32
|
+
* A UTC key would move late-evening work onto the next day for anyone west
|
|
33
|
+
* of Greenwich and onto the previous day for anyone east of it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatLocalDateKey(value?: Date | number): string;
|
|
36
|
+
/** Parse a `YYYY-MM-DD` key produced by `formatLocalDateKey` back into local midnight. */
|
|
37
|
+
export declare function parseLocalDateKey(key: string): Date | null;
|
|
38
|
+
/** Shift a `YYYY-MM-DD` key by whole local days (DST-safe). */
|
|
39
|
+
export declare function addLocalDays(key: string, days: number): string;
|
package/dist/formatting.js
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.formatTokenCount = formatTokenCount;
|
|
12
12
|
exports.formatDurationMs = formatDurationMs;
|
|
13
|
+
exports.formatLocalDateKey = formatLocalDateKey;
|
|
14
|
+
exports.parseLocalDateKey = parseLocalDateKey;
|
|
15
|
+
exports.addLocalDays = addLocalDays;
|
|
13
16
|
/** Format a token count with compact `k` / `M` suffixes. */
|
|
14
17
|
function formatTokenCount(value, options = {}) {
|
|
15
18
|
if (!Number.isFinite(value))
|
|
@@ -52,3 +55,35 @@ function formatDurationMs(ms, options = {}) {
|
|
|
52
55
|
}
|
|
53
56
|
return `${minutes}m${separator}${remainingSeconds}s`;
|
|
54
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Calendar-date key (`YYYY-MM-DD`) in the machine's local time zone.
|
|
60
|
+
*
|
|
61
|
+
* Sidekick buckets "today", daily history, and heatmaps by the user's local
|
|
62
|
+
* day, so every writer and reader of a date key goes through this helper.
|
|
63
|
+
* A UTC key would move late-evening work onto the next day for anyone west
|
|
64
|
+
* of Greenwich and onto the previous day for anyone east of it.
|
|
65
|
+
*/
|
|
66
|
+
function formatLocalDateKey(value = new Date()) {
|
|
67
|
+
const date = typeof value === 'number' ? new Date(value) : value;
|
|
68
|
+
if (Number.isNaN(date.getTime()))
|
|
69
|
+
return '';
|
|
70
|
+
const year = date.getFullYear();
|
|
71
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
72
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
73
|
+
return `${year}-${month}-${day}`;
|
|
74
|
+
}
|
|
75
|
+
/** Parse a `YYYY-MM-DD` key produced by `formatLocalDateKey` back into local midnight. */
|
|
76
|
+
function parseLocalDateKey(key) {
|
|
77
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
|
|
78
|
+
if (!match)
|
|
79
|
+
return null;
|
|
80
|
+
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
81
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
82
|
+
}
|
|
83
|
+
/** Shift a `YYYY-MM-DD` key by whole local days (DST-safe). */
|
|
84
|
+
function addLocalDays(key, days) {
|
|
85
|
+
const date = parseLocalDateKey(key);
|
|
86
|
+
if (!date)
|
|
87
|
+
return key;
|
|
88
|
+
return formatLocalDateKey(new Date(date.getFullYear(), date.getMonth(), date.getDate() + days));
|
|
89
|
+
}
|