sidekick-shared 0.25.0 → 0.26.1
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 +65 -0
- package/README.md +50 -3
- package/dist/accountChanges.js +22 -3
- package/dist/accountRegistry.d.ts +22 -1
- package/dist/accountRegistry.js +37 -14
- 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 +11 -1
- package/dist/browser.js +29 -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/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 +46 -12
- package/dist/index.js +112 -24
- package/dist/modelContext.js +8 -3
- package/dist/modelInfo.js +45 -27
- package/dist/node.d.ts +19 -0
- package/dist/node.js +47 -1
- package/dist/observedContextWindows.d.ts +7 -0
- package/dist/observedContextWindows.js +17 -2
- package/dist/observedSessionCollector.d.ts +3 -8
- package/dist/observedSessionCollector.js +10 -27
- package/dist/parsers/sessionActivityDetector.d.ts +24 -2
- package/dist/parsers/sessionActivityDetector.js +95 -17
- 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/providers/claudeCode.js +11 -96
- package/dist/providers/codex.d.ts +2 -0
- package/dist/providers/codex.js +94 -259
- package/dist/providers/detect.d.ts +2 -0
- package/dist/providers/detect.js +29 -24
- package/dist/providers/factory.d.ts +6 -0
- package/dist/providers/openCode.d.ts +2 -0
- package/dist/providers/openCode.js +105 -121
- package/dist/providers/rolloutWalker.d.ts +53 -0
- package/dist/providers/rolloutWalker.js +209 -0
- package/dist/providers/types.d.ts +30 -0
- package/dist/quota.d.ts +30 -3
- package/dist/quota.js +35 -3
- package/dist/quotaHistory.d.ts +1 -1
- package/dist/quotaHistory.js +36 -35
- package/dist/quotaPresentation.js +17 -5
- package/dist/quotaResolver.d.ts +83 -0
- package/dist/quotaResolver.js +234 -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/index.d.ts +2 -0
- package/dist/schemas/index.js +10 -1
- package/dist/schemas/quota.d.ts +139 -0
- package/dist/schemas/quota.js +17 -2
- package/dist/schemas/quotaHistory.d.ts +1 -0
- 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/transcript.js +2 -0
- package/dist/sessionFingerprint.d.ts +25 -0
- package/dist/sessionFingerprint.js +46 -0
- package/dist/sessionPreviews.d.ts +7 -1
- package/dist/sessionPreviews.js +15 -3
- 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 +2 -0
- package/dist/types/observedSessionV1.js +75 -18
- 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 +1 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { ActiveAccountStatus } from '../accountStatus';
|
|
2
2
|
import type { QuotaState } from '../quota';
|
|
3
|
+
import type { ClaudeStatuslinePayload } from './claudeStatuslinePayload';
|
|
3
4
|
export interface StatuslineInput {
|
|
4
5
|
accounts: ActiveAccountStatus;
|
|
5
6
|
claudeQuota?: QuotaState | null;
|
|
6
7
|
codexQuota?: QuotaState | null;
|
|
8
|
+
/**
|
|
9
|
+
* The document Claude Code piped to the status-line command, when running
|
|
10
|
+
* as one. Adds context usage, session cost, and prompt-cache hit rate.
|
|
11
|
+
*/
|
|
12
|
+
live?: ClaudeStatuslinePayload | null;
|
|
7
13
|
now?: Date;
|
|
8
14
|
}
|
|
9
15
|
export interface StatuslineSelection {
|
|
@@ -13,4 +19,11 @@ export interface StatuslineSelection {
|
|
|
13
19
|
quota: QuotaState | null;
|
|
14
20
|
}
|
|
15
21
|
export declare function selectStatuslineAccount(input: StatuslineInput): StatuslineSelection | null;
|
|
22
|
+
/**
|
|
23
|
+
* One-line status. Segments, in order, each omitted when unknown:
|
|
24
|
+
* `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
|
|
25
|
+
*
|
|
26
|
+
* The age segment appears only for cached quota older than five minutes, so
|
|
27
|
+
* a snapshot from yesterday can no longer read like a live figure.
|
|
28
|
+
*/
|
|
16
29
|
export declare function formatStatusline(input: StatuslineInput): string;
|
|
@@ -23,20 +23,62 @@ function selectStatuslineAccount(input) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* One-line status. Segments, in order, each omitted when unknown:
|
|
28
|
+
* `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
|
|
29
|
+
*
|
|
30
|
+
* The age segment appears only for cached quota older than five minutes, so
|
|
31
|
+
* a snapshot from yesterday can no longer read like a live figure.
|
|
32
|
+
*/
|
|
26
33
|
function formatStatusline(input) {
|
|
27
34
|
const selected = selectStatuslineAccount(input);
|
|
35
|
+
const now = input.now ?? new Date();
|
|
36
|
+
const liveSegments = formatLiveSegments(input.live);
|
|
28
37
|
if (!selected)
|
|
29
|
-
return 'acct:none
|
|
38
|
+
return joinSegments(['acct:none', 'quota unavailable', ...liveSegments]);
|
|
30
39
|
const account = compactLabel(selected.accountLabel);
|
|
31
40
|
const quota = selected.quota;
|
|
32
|
-
if (!quota?.available)
|
|
33
|
-
return `acct:${account}
|
|
34
|
-
|
|
41
|
+
if (!quota?.available) {
|
|
42
|
+
return joinSegments([`acct:${account}`, 'quota unavailable', ...liveSegments]);
|
|
43
|
+
}
|
|
35
44
|
const utilization = clampPercent(quota.fiveHour.utilization);
|
|
36
45
|
const reset = formatReset(quota.fiveHour.resetsAt, now);
|
|
37
46
|
const eta = estimateWindowEta(quota, now);
|
|
38
|
-
const
|
|
39
|
-
|
|
47
|
+
const segments = [`acct:${account}`, `5h ${Math.round(utilization)}% resets ${reset}`];
|
|
48
|
+
if (eta != null)
|
|
49
|
+
segments.push(`~${formatMinutes(eta)} left`);
|
|
50
|
+
if (quota.sevenDay.resetsAt && quota.sevenDay.utilization > 0) {
|
|
51
|
+
segments.push(`7d ${Math.round(clampPercent(quota.sevenDay.utilization))}%`);
|
|
52
|
+
}
|
|
53
|
+
if (quota.freshness && quota.freshness !== 'fresh') {
|
|
54
|
+
segments.push(`(${(0, quota_1.formatQuotaAge)(quota.ageMs)})`);
|
|
55
|
+
}
|
|
56
|
+
return joinSegments([...segments, ...liveSegments]);
|
|
57
|
+
}
|
|
58
|
+
function formatLiveSegments(live) {
|
|
59
|
+
if (!live)
|
|
60
|
+
return [];
|
|
61
|
+
const segments = [];
|
|
62
|
+
const context = live.contextWindow?.usedPercentage;
|
|
63
|
+
if (typeof context === 'number')
|
|
64
|
+
segments.push(`ctx ${Math.round(clampPercent(context))}%`);
|
|
65
|
+
const cost = live.cost?.totalCostUsd;
|
|
66
|
+
if (typeof cost === 'number' && cost > 0)
|
|
67
|
+
segments.push(`$${formatCost(cost)}`);
|
|
68
|
+
const hitRatio = live.promptCache?.hitRatio;
|
|
69
|
+
if (typeof hitRatio === 'number')
|
|
70
|
+
segments.push(`cache ${Math.round(clampPercent(hitRatio * 100))}%`);
|
|
71
|
+
return segments;
|
|
72
|
+
}
|
|
73
|
+
function joinSegments(segments) {
|
|
74
|
+
return segments.join(' · ');
|
|
75
|
+
}
|
|
76
|
+
function formatCost(value) {
|
|
77
|
+
if (value >= 100)
|
|
78
|
+
return value.toFixed(0);
|
|
79
|
+
if (value >= 10)
|
|
80
|
+
return value.toFixed(1);
|
|
81
|
+
return value.toFixed(2);
|
|
40
82
|
}
|
|
41
83
|
function estimateWindowEta(quota, now) {
|
|
42
84
|
const resetAt = Date.parse(quota.fiveHour.resetsAt);
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
export { getActiveAccountStatus } from '../accountStatus';
|
|
2
|
-
export type { ActiveAccountStatus, ActiveProviderAccountStatus } from '../accountStatus';
|
|
3
|
-
export { readQuotaSnapshot } from '../quotaSnapshots';
|
|
2
|
+
export type { ActiveAccountStatus, ActiveAccountStatusOptions, ActiveProviderAccountStatus, } from '../accountStatus';
|
|
3
|
+
export { readQuotaSnapshot, writeQuotaSnapshot } from '../quotaSnapshots';
|
|
4
4
|
export type { QuotaSnapshotProviderId } from '../quotaSnapshots';
|
|
5
|
+
export { appendQuotaHistorySample, getWorkspaceIdFromPath } from '../quotaHistory';
|
|
6
|
+
export type { QuotaHistorySample } from '../quotaHistory';
|
|
7
|
+
export { classifyQuotaFreshness, formatQuotaAge } from '../quota';
|
|
8
|
+
export type { QuotaFreshness, QuotaState } from '../quota';
|
|
5
9
|
export { BurnRateCalculator, estimateTimeToQuota } from './BurnRateCalculator';
|
|
6
10
|
export { formatStatusline, selectStatuslineAccount } from './formatter';
|
|
7
11
|
export type { StatuslineInput, StatuslineSelection } from './formatter';
|
|
12
|
+
export { parseClaudeStatuslinePayload, quotaFromStatuslinePayload, } from './claudeStatuslinePayload';
|
|
13
|
+
export type { ClaudeStatuslinePayload, ClaudeStatuslineRateLimitWindow, QuotaFromStatuslineOptions, } from './claudeStatuslinePayload';
|
|
14
|
+
export { STATE_FILE_NAME, STATE_FILE_SCHEMA_VERSION, billingBlockToStateFile, getStateFilePath, quotaToStateFile, readStateFile, writeStateFile, } from '../stateFile';
|
|
15
|
+
export type { SidekickStateFile, SidekickStateInput, StateFileAccount, StateFileBillingBlock, StateFileContext, StateFileQuota, StateFileQuotaWindow, StateFileSession, StateFileWriter, WriteStateFileOptions, } from '../stateFile';
|
package/dist/statusline/index.js
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
|
|
3
|
+
exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.formatQuotaAge = exports.classifyQuotaFreshness = exports.getWorkspaceIdFromPath = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
|
|
4
4
|
var accountStatus_1 = require("../accountStatus");
|
|
5
5
|
Object.defineProperty(exports, "getActiveAccountStatus", { enumerable: true, get: function () { return accountStatus_1.getActiveAccountStatus; } });
|
|
6
6
|
var quotaSnapshots_1 = require("../quotaSnapshots");
|
|
7
7
|
Object.defineProperty(exports, "readQuotaSnapshot", { enumerable: true, get: function () { return quotaSnapshots_1.readQuotaSnapshot; } });
|
|
8
|
+
Object.defineProperty(exports, "writeQuotaSnapshot", { enumerable: true, get: function () { return quotaSnapshots_1.writeQuotaSnapshot; } });
|
|
9
|
+
var quotaHistory_1 = require("../quotaHistory");
|
|
10
|
+
Object.defineProperty(exports, "appendQuotaHistorySample", { enumerable: true, get: function () { return quotaHistory_1.appendQuotaHistorySample; } });
|
|
11
|
+
Object.defineProperty(exports, "getWorkspaceIdFromPath", { enumerable: true, get: function () { return quotaHistory_1.getWorkspaceIdFromPath; } });
|
|
12
|
+
var quota_1 = require("../quota");
|
|
13
|
+
Object.defineProperty(exports, "classifyQuotaFreshness", { enumerable: true, get: function () { return quota_1.classifyQuotaFreshness; } });
|
|
14
|
+
Object.defineProperty(exports, "formatQuotaAge", { enumerable: true, get: function () { return quota_1.formatQuotaAge; } });
|
|
8
15
|
var BurnRateCalculator_1 = require("./BurnRateCalculator");
|
|
9
16
|
Object.defineProperty(exports, "BurnRateCalculator", { enumerable: true, get: function () { return BurnRateCalculator_1.BurnRateCalculator; } });
|
|
10
17
|
Object.defineProperty(exports, "estimateTimeToQuota", { enumerable: true, get: function () { return BurnRateCalculator_1.estimateTimeToQuota; } });
|
|
11
18
|
var formatter_1 = require("./formatter");
|
|
12
19
|
Object.defineProperty(exports, "formatStatusline", { enumerable: true, get: function () { return formatter_1.formatStatusline; } });
|
|
13
20
|
Object.defineProperty(exports, "selectStatuslineAccount", { enumerable: true, get: function () { return formatter_1.selectStatuslineAccount; } });
|
|
21
|
+
var claudeStatuslinePayload_1 = require("./claudeStatuslinePayload");
|
|
22
|
+
Object.defineProperty(exports, "parseClaudeStatuslinePayload", { enumerable: true, get: function () { return claudeStatuslinePayload_1.parseClaudeStatuslinePayload; } });
|
|
23
|
+
Object.defineProperty(exports, "quotaFromStatuslinePayload", { enumerable: true, get: function () { return claudeStatuslinePayload_1.quotaFromStatuslinePayload; } });
|
|
24
|
+
var stateFile_1 = require("../stateFile");
|
|
25
|
+
Object.defineProperty(exports, "STATE_FILE_NAME", { enumerable: true, get: function () { return stateFile_1.STATE_FILE_NAME; } });
|
|
26
|
+
Object.defineProperty(exports, "STATE_FILE_SCHEMA_VERSION", { enumerable: true, get: function () { return stateFile_1.STATE_FILE_SCHEMA_VERSION; } });
|
|
27
|
+
Object.defineProperty(exports, "billingBlockToStateFile", { enumerable: true, get: function () { return stateFile_1.billingBlockToStateFile; } });
|
|
28
|
+
Object.defineProperty(exports, "getStateFilePath", { enumerable: true, get: function () { return stateFile_1.getStateFilePath; } });
|
|
29
|
+
Object.defineProperty(exports, "quotaToStateFile", { enumerable: true, get: function () { return stateFile_1.quotaToStateFile; } });
|
|
30
|
+
Object.defineProperty(exports, "readStateFile", { enumerable: true, get: function () { return stateFile_1.readStateFile; } });
|
|
31
|
+
Object.defineProperty(exports, "writeStateFile", { enumerable: true, get: function () { return stateFile_1.writeStateFile; } });
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One vocabulary for token totals across every Sidekick surface.
|
|
3
|
+
*
|
|
4
|
+
* Every report, dashboard, status bar, and export derives its headline token
|
|
5
|
+
* numbers from here so "Total" means the same thing everywhere:
|
|
6
|
+
*
|
|
7
|
+
* - `total` — every token the provider billed: uncached input, cache writes,
|
|
8
|
+
* cache reads, and output. Matches `NormalizedUsage.totalTokens`
|
|
9
|
+
* and the per-model `tokens` the `EventAggregator` keeps.
|
|
10
|
+
* - `context` — tokens that occupied the context window on the last request:
|
|
11
|
+
* uncached input, cache writes, and cache reads. Output is
|
|
12
|
+
* excluded, matching the input-only formula Claude Code uses for
|
|
13
|
+
* its status-line `used_percentage`.
|
|
14
|
+
* - `output` — output tokens as billed.
|
|
15
|
+
*
|
|
16
|
+
* Cache reads are usually the majority of a coding-agent session's tokens, so
|
|
17
|
+
* an "input + output" total silently drops most of the work. Sidekick used to
|
|
18
|
+
* do that on some surfaces and not others; this helper is the single place the
|
|
19
|
+
* arithmetic lives.
|
|
20
|
+
*
|
|
21
|
+
* Browser-safe: no Node imports.
|
|
22
|
+
*
|
|
23
|
+
* @module tokenSummary
|
|
24
|
+
*/
|
|
25
|
+
/** Any object carrying the four billable token buckets. */
|
|
26
|
+
export interface TokenTotalsLike {
|
|
27
|
+
inputTokens: number;
|
|
28
|
+
outputTokens: number;
|
|
29
|
+
cacheWriteTokens?: number;
|
|
30
|
+
cacheReadTokens?: number;
|
|
31
|
+
/**
|
|
32
|
+
* A provider-semantics-aware total, when the source already computed one
|
|
33
|
+
* (for example `AggregatedTokens.totalTokens`). Preferred over the
|
|
34
|
+
* four-bucket sum because it already accounts for whether reasoning tokens
|
|
35
|
+
* were billed inside or outside `outputTokens`.
|
|
36
|
+
*/
|
|
37
|
+
totalTokens?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface TokenSummary {
|
|
40
|
+
/** Everything billed: uncached input + cache writes + cache reads + output. */
|
|
41
|
+
total: number;
|
|
42
|
+
/** Tokens occupying the context window: uncached input + cache writes + cache reads. */
|
|
43
|
+
context: number;
|
|
44
|
+
/** Uncached input tokens. */
|
|
45
|
+
input: number;
|
|
46
|
+
/** Output tokens as billed. */
|
|
47
|
+
output: number;
|
|
48
|
+
/** Tokens read from the prompt cache. */
|
|
49
|
+
cacheRead: number;
|
|
50
|
+
/** Tokens written to the prompt cache. */
|
|
51
|
+
cacheWrite: number;
|
|
52
|
+
/** Fraction of context tokens that came from cache reads (0..1), or null with no input. */
|
|
53
|
+
cacheHitRatio: number | null;
|
|
54
|
+
}
|
|
55
|
+
/** Column label for `TokenSummary.total` on any surface. */
|
|
56
|
+
export declare const TOKEN_TOTAL_LABEL = "Total (incl. cache)";
|
|
57
|
+
/** Column label for `TokenSummary.context` on any surface. */
|
|
58
|
+
export declare const TOKEN_CONTEXT_LABEL = "Context";
|
|
59
|
+
/** Derive the shared token vocabulary from any four-bucket token record. */
|
|
60
|
+
export declare function summarizeTokens(totals: TokenTotalsLike): TokenSummary;
|
|
61
|
+
/** Sum several token records bucket-by-bucket before summarizing. */
|
|
62
|
+
export declare function sumTokenTotals(records: readonly TokenTotalsLike[]): TokenTotalsLike;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One vocabulary for token totals across every Sidekick surface.
|
|
4
|
+
*
|
|
5
|
+
* Every report, dashboard, status bar, and export derives its headline token
|
|
6
|
+
* numbers from here so "Total" means the same thing everywhere:
|
|
7
|
+
*
|
|
8
|
+
* - `total` — every token the provider billed: uncached input, cache writes,
|
|
9
|
+
* cache reads, and output. Matches `NormalizedUsage.totalTokens`
|
|
10
|
+
* and the per-model `tokens` the `EventAggregator` keeps.
|
|
11
|
+
* - `context` — tokens that occupied the context window on the last request:
|
|
12
|
+
* uncached input, cache writes, and cache reads. Output is
|
|
13
|
+
* excluded, matching the input-only formula Claude Code uses for
|
|
14
|
+
* its status-line `used_percentage`.
|
|
15
|
+
* - `output` — output tokens as billed.
|
|
16
|
+
*
|
|
17
|
+
* Cache reads are usually the majority of a coding-agent session's tokens, so
|
|
18
|
+
* an "input + output" total silently drops most of the work. Sidekick used to
|
|
19
|
+
* do that on some surfaces and not others; this helper is the single place the
|
|
20
|
+
* arithmetic lives.
|
|
21
|
+
*
|
|
22
|
+
* Browser-safe: no Node imports.
|
|
23
|
+
*
|
|
24
|
+
* @module tokenSummary
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.TOKEN_CONTEXT_LABEL = exports.TOKEN_TOTAL_LABEL = void 0;
|
|
28
|
+
exports.summarizeTokens = summarizeTokens;
|
|
29
|
+
exports.sumTokenTotals = sumTokenTotals;
|
|
30
|
+
/** Column label for `TokenSummary.total` on any surface. */
|
|
31
|
+
exports.TOKEN_TOTAL_LABEL = 'Total (incl. cache)';
|
|
32
|
+
/** Column label for `TokenSummary.context` on any surface. */
|
|
33
|
+
exports.TOKEN_CONTEXT_LABEL = 'Context';
|
|
34
|
+
function count(value) {
|
|
35
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
36
|
+
}
|
|
37
|
+
/** Derive the shared token vocabulary from any four-bucket token record. */
|
|
38
|
+
function summarizeTokens(totals) {
|
|
39
|
+
const input = count(totals.inputTokens);
|
|
40
|
+
const output = count(totals.outputTokens);
|
|
41
|
+
const cacheRead = count(totals.cacheReadTokens);
|
|
42
|
+
const cacheWrite = count(totals.cacheWriteTokens);
|
|
43
|
+
const context = input + cacheWrite + cacheRead;
|
|
44
|
+
const fourBucket = context + output;
|
|
45
|
+
const provided = totals.totalTokens;
|
|
46
|
+
const total = typeof provided === 'number' && Number.isFinite(provided) && provided >= 0
|
|
47
|
+
? Math.floor(provided)
|
|
48
|
+
: fourBucket;
|
|
49
|
+
return {
|
|
50
|
+
total,
|
|
51
|
+
context,
|
|
52
|
+
input,
|
|
53
|
+
output,
|
|
54
|
+
cacheRead,
|
|
55
|
+
cacheWrite,
|
|
56
|
+
cacheHitRatio: context > 0 ? cacheRead / context : null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Sum several token records bucket-by-bucket before summarizing. */
|
|
60
|
+
function sumTokenTotals(records) {
|
|
61
|
+
let inputTokens = 0;
|
|
62
|
+
let outputTokens = 0;
|
|
63
|
+
let cacheWriteTokens = 0;
|
|
64
|
+
let cacheReadTokens = 0;
|
|
65
|
+
for (const record of records) {
|
|
66
|
+
inputTokens += count(record.inputTokens);
|
|
67
|
+
outputTokens += count(record.outputTokens);
|
|
68
|
+
cacheWriteTokens += count(record.cacheWriteTokens);
|
|
69
|
+
cacheReadTokens += count(record.cacheReadTokens);
|
|
70
|
+
}
|
|
71
|
+
return { inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
|
|
72
|
+
}
|
package/dist/transcript.d.ts
CHANGED
|
@@ -77,7 +77,12 @@ export interface CanonicalSessionTranscript {
|
|
|
77
77
|
events: NormalizedUsage[];
|
|
78
78
|
costs: NormalizedUsageCost[];
|
|
79
79
|
totals: CanonicalTranscriptUsageTotals;
|
|
80
|
+
/** Strict total: `null` unless every usage event could be priced. */
|
|
80
81
|
totalCostUsd: number | null;
|
|
82
|
+
/** Sum of the events that could be priced; pair with `unpricedEvents`. */
|
|
83
|
+
pricedCostUsd: number;
|
|
84
|
+
/** Usage events with neither a reported cost nor a catalog price. */
|
|
85
|
+
unpricedEvents: number;
|
|
81
86
|
};
|
|
82
87
|
provenance: {
|
|
83
88
|
source: 'session-events';
|
package/dist/transcript.js
CHANGED
|
@@ -144,6 +144,8 @@ function projectSessionTranscript(events, options = {}) {
|
|
|
144
144
|
totalCostUsd: usageCosts.length > 0 && usageCosts.every((cost) => cost.costUsd !== null)
|
|
145
145
|
? usageCosts.reduce((sum, cost) => sum + (cost.costUsd ?? 0), 0)
|
|
146
146
|
: null,
|
|
147
|
+
pricedCostUsd: usageCosts.reduce((sum, cost) => sum + (cost.costUsd ?? 0), 0),
|
|
148
|
+
unpricedEvents: usageCosts.filter((cost) => cost.costUsd === null).length,
|
|
147
149
|
},
|
|
148
150
|
provenance: { source: 'session-events', fidelity, eventCount: events.length },
|
|
149
151
|
};
|
|
@@ -69,6 +69,8 @@ export interface ProviderSessionAdapterV1 {
|
|
|
69
69
|
dispose(): void;
|
|
70
70
|
}
|
|
71
71
|
export declare function derivePendingUserRequestV1(provider: ProviderId, sessionId: string, sourcePath: string, events: SessionEvent[]): PendingUserRequestV1 | null;
|
|
72
|
+
/** The activity detector's reason for a session produced by this adapter, if known. */
|
|
73
|
+
export declare function getObservedActivityReason(session: ObservedAgentSessionV1): string | undefined;
|
|
72
74
|
export interface ProviderSessionAdapterV1Options {
|
|
73
75
|
pollIntervalMs?: number;
|
|
74
76
|
observationOnly?: boolean;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.derivePendingUserRequestV1 = derivePendingUserRequestV1;
|
|
4
|
+
exports.getObservedActivityReason = getObservedActivityReason;
|
|
4
5
|
exports.createProviderSessionAdapterV1 = createProviderSessionAdapterV1;
|
|
5
6
|
const transcript_1 = require("../transcript");
|
|
6
7
|
const sessionActivityDetector_1 = require("../parsers/sessionActivityDetector");
|
|
8
|
+
const sessionStats_1 = require("../sessionStats");
|
|
9
|
+
const sessionFingerprint_1 = require("../sessionFingerprint");
|
|
7
10
|
function evidence(provider, sessionId, sourcePath, event, eventIndex) {
|
|
8
11
|
return [
|
|
9
12
|
{
|
|
@@ -111,6 +114,16 @@ function providerCapabilities(provider, observationOnly) {
|
|
|
111
114
|
assetExtraction: known(true),
|
|
112
115
|
};
|
|
113
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Detector reason behind each observed session's activity, kept off the
|
|
119
|
+
* versioned V1 shape so cache refreshes can tell a grace-period "active" from
|
|
120
|
+
* a genuinely ongoing one.
|
|
121
|
+
*/
|
|
122
|
+
const activityReasons = new WeakMap();
|
|
123
|
+
/** The activity detector's reason for a session produced by this adapter, if known. */
|
|
124
|
+
function getObservedActivityReason(session) {
|
|
125
|
+
return activityReasons.get(session);
|
|
126
|
+
}
|
|
114
127
|
function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
115
128
|
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
|
|
116
129
|
const adapter = {
|
|
@@ -121,16 +134,28 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
121
134
|
return Promise.all(sessionProvider.findAllSessions(cwd).map((sessionPath) => adapter.read(sessionPath, cwd)));
|
|
122
135
|
},
|
|
123
136
|
async read(sessionPath, cwd = '') {
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
// One reader pass feeds stats, transcript, activity, and the pending
|
|
138
|
+
// request; the reader is flushed so a trailing partial line is included.
|
|
139
|
+
const sessionId = sessionProvider.getSessionId(sessionPath);
|
|
140
|
+
const reader = sessionProvider.createReader(sessionPath);
|
|
141
|
+
const events = reader.readAll();
|
|
142
|
+
reader.flush();
|
|
143
|
+
const stats = (0, sessionStats_1.computeSessionFileStats)(events, {
|
|
144
|
+
providerId: sessionProvider.id,
|
|
145
|
+
sessionId,
|
|
146
|
+
filePath: sessionPath,
|
|
147
|
+
label: '',
|
|
148
|
+
computeContextSize: (0, sessionStats_1.providerContextSizeFn)(sessionProvider),
|
|
149
|
+
});
|
|
126
150
|
const transcript = (0, transcript_1.projectSessionTranscript)(events, {
|
|
127
151
|
provider: sessionProvider.id,
|
|
128
|
-
sessionId
|
|
152
|
+
sessionId,
|
|
129
153
|
sourcePath: sessionPath,
|
|
130
154
|
});
|
|
131
|
-
const refs = evidence(sessionProvider.id,
|
|
155
|
+
const refs = evidence(sessionProvider.id, sessionId, sessionPath);
|
|
132
156
|
const model = Object.entries(stats.modelUsage).sort((left, right) => right[1].calls - left[1].calls)[0]?.[0] ?? null;
|
|
133
|
-
const
|
|
157
|
+
const parts = (0, sessionFingerprint_1.sessionFingerprintParts)(sessionProvider, sessionPath);
|
|
158
|
+
const activityResult = (0, sessionActivityDetector_1.classifySessionActivity)({ events, mtimeMs: parts?.mtimeMs ?? null });
|
|
134
159
|
const activity = activityResult.state === 'ongoing'
|
|
135
160
|
? 'active'
|
|
136
161
|
: activityResult.state === 'stale'
|
|
@@ -139,11 +164,11 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
139
164
|
const allCostsReported = transcript.usage.costs.length > 0 &&
|
|
140
165
|
transcript.usage.costs.every((cost) => cost.source === 'provider-reported');
|
|
141
166
|
const observedAt = new Date().toISOString();
|
|
142
|
-
|
|
167
|
+
const session = {
|
|
143
168
|
schemaVersion: 1,
|
|
144
169
|
identity: {
|
|
145
170
|
provider: sessionProvider.id,
|
|
146
|
-
sessionId
|
|
171
|
+
sessionId,
|
|
147
172
|
sourcePath: sessionPath,
|
|
148
173
|
},
|
|
149
174
|
cwd: observed(cwd, 'inferred', cwd ? 0.95 : 0.4, refs),
|
|
@@ -161,29 +186,61 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
161
186
|
? 'inferred'
|
|
162
187
|
: 'estimated', allCostsReported ? 1 : transcript.usage.totalCostUsd === null ? 0.5 : 0.9, refs),
|
|
163
188
|
},
|
|
164
|
-
pendingUserRequest: derivePendingUserRequestV1(sessionProvider.id,
|
|
189
|
+
pendingUserRequest: derivePendingUserRequestV1(sessionProvider.id, sessionId, sessionPath, events),
|
|
165
190
|
contentObservedAt: observedAt,
|
|
166
191
|
observedAt,
|
|
167
192
|
};
|
|
193
|
+
activityReasons.set(session, activityResult.reason);
|
|
194
|
+
return session;
|
|
168
195
|
},
|
|
169
196
|
watch(sessionPath, listener, cwd) {
|
|
170
197
|
let disposed = false;
|
|
171
198
|
let lastSignature = '';
|
|
199
|
+
let lastFingerprint = null;
|
|
200
|
+
let lastSession = null;
|
|
201
|
+
const emitIfChanged = (session) => {
|
|
202
|
+
const signature = JSON.stringify({
|
|
203
|
+
activity: session.activity,
|
|
204
|
+
model: session.model,
|
|
205
|
+
usage: session.usage,
|
|
206
|
+
pendingUserRequest: session.pendingUserRequest,
|
|
207
|
+
});
|
|
208
|
+
if (signature !== lastSignature) {
|
|
209
|
+
lastSignature = signature;
|
|
210
|
+
listener(session);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
172
213
|
const update = async () => {
|
|
173
214
|
if (disposed)
|
|
174
215
|
return;
|
|
175
216
|
try {
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
const parts = (0, sessionFingerprint_1.sessionFingerprintParts)(sessionProvider, sessionPath);
|
|
218
|
+
const fingerprint = parts ? (0, sessionFingerprint_1.fingerprintString)(parts) : null;
|
|
219
|
+
if (lastSession && parts && fingerprint === lastFingerprint) {
|
|
220
|
+
// Unchanged content: only the activity can drift with time, so
|
|
221
|
+
// refresh it from the cached classification instead of re-reading.
|
|
222
|
+
const previousReason = activityReasons.get(lastSession);
|
|
223
|
+
const refreshed = (0, sessionActivityDetector_1.refreshSessionActivityState)(lastSession.activity.value, parts.mtimeMs, Date.now(), previousReason);
|
|
224
|
+
if (refreshed !== lastSession.activity.value) {
|
|
225
|
+
const session = {
|
|
226
|
+
...lastSession,
|
|
227
|
+
activity: { ...lastSession.activity, value: refreshed },
|
|
228
|
+
observedAt: new Date().toISOString(),
|
|
229
|
+
};
|
|
230
|
+
activityReasons.set(session, refreshed === 'idle'
|
|
231
|
+
? 'mtime-stale'
|
|
232
|
+
: previousReason === 'grace-period'
|
|
233
|
+
? 'ending-event'
|
|
234
|
+
: 'no-activity-signal');
|
|
235
|
+
lastSession = session;
|
|
236
|
+
emitIfChanged(session);
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
186
239
|
}
|
|
240
|
+
const session = await adapter.read(sessionPath, cwd);
|
|
241
|
+
lastFingerprint = fingerprint;
|
|
242
|
+
lastSession = session;
|
|
243
|
+
emitIfChanged(session);
|
|
187
244
|
}
|
|
188
245
|
catch {
|
|
189
246
|
// Observed sources can disappear between polling iterations.
|
|
@@ -47,6 +47,8 @@ export interface SessionMessage {
|
|
|
47
47
|
normalizedUsage?: import('../usageNormalization').NormalizedUsage;
|
|
48
48
|
/** Message content (may be string or structured content) */
|
|
49
49
|
content?: unknown;
|
|
50
|
+
/** Provider stop reason (`end_turn`, `tool_use`, …) when the transcript carries one. */
|
|
51
|
+
stop_reason?: string;
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Canonical session event from JSONL/DB log files.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Five-hour billing blocks computed from usage events.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the ccusage convention: a block opens at the first usage event
|
|
5
|
+
* (aligned down to the UTC hour), lasts five hours, and an event that lands
|
|
6
|
+
* after the block's end or more than five hours after the previous event opens
|
|
7
|
+
* a new block. The active block also reports a burn rate and an end-of-block
|
|
8
|
+
* projection. Browser-safe: no Node imports.
|
|
9
|
+
*
|
|
10
|
+
* @module usage/billingBlocks
|
|
11
|
+
*/
|
|
12
|
+
import type { AggregatedCostProvenance } from '../aggregation/types';
|
|
13
|
+
import type { TokenTotalsLike } from '../tokenSummary';
|
|
14
|
+
import type { PricingProvenance } from '../usageNormalization';
|
|
15
|
+
/** Length of one billing block. */
|
|
16
|
+
export declare const BILLING_BLOCK_DURATION_MS: number;
|
|
17
|
+
/** Minimal usage event the block calculator needs. */
|
|
18
|
+
export interface BillingBlockInput {
|
|
19
|
+
/** Event time (ms since epoch, ISO string, or Date). */
|
|
20
|
+
timestamp: number | string | Date;
|
|
21
|
+
tokens: TokenTotalsLike;
|
|
22
|
+
/** Priced cost for the event, or null when it could not be priced. */
|
|
23
|
+
costUsd: number | null;
|
|
24
|
+
costProvenance?: PricingProvenance;
|
|
25
|
+
model?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface BillingBlockTokens {
|
|
28
|
+
inputTokens: number;
|
|
29
|
+
outputTokens: number;
|
|
30
|
+
cacheWriteTokens: number;
|
|
31
|
+
cacheReadTokens: number;
|
|
32
|
+
/** Cache-inclusive total (`summarizeTokens().total`). */
|
|
33
|
+
total: number;
|
|
34
|
+
}
|
|
35
|
+
export interface BillingBlockModelUsage {
|
|
36
|
+
calls: number;
|
|
37
|
+
/** Cache-inclusive tokens. */
|
|
38
|
+
tokens: number;
|
|
39
|
+
costUsd: number;
|
|
40
|
+
}
|
|
41
|
+
export interface BillingBlock {
|
|
42
|
+
/** Block identifier: the ISO start time. */
|
|
43
|
+
id: string;
|
|
44
|
+
/** ISO start (aligned to the hour when `alignToHour` is set). */
|
|
45
|
+
start: string;
|
|
46
|
+
/** ISO end (`start` + block duration). */
|
|
47
|
+
end: string;
|
|
48
|
+
firstEvent: string;
|
|
49
|
+
lastEvent: string;
|
|
50
|
+
/** `now` is inside the block and within one block length of the last event. */
|
|
51
|
+
isActive: boolean;
|
|
52
|
+
calls: number;
|
|
53
|
+
tokens: BillingBlockTokens;
|
|
54
|
+
costUsd: number;
|
|
55
|
+
costProvenance: AggregatedCostProvenance;
|
|
56
|
+
unpricedCalls: number;
|
|
57
|
+
models: Record<string, BillingBlockModelUsage>;
|
|
58
|
+
/** Time from the first event to `now` (active) or to the last event (closed). */
|
|
59
|
+
elapsedMs: number;
|
|
60
|
+
/** Time left until `end` for the active block; 0 otherwise. */
|
|
61
|
+
remainingMs: number;
|
|
62
|
+
/** Tokens per minute over `elapsedMs`. */
|
|
63
|
+
burnRatePerMinute: number;
|
|
64
|
+
/** Tokens expected by `end` at the current burn rate (equals `tokens.total` for closed blocks). */
|
|
65
|
+
projectedTokens: number;
|
|
66
|
+
/** Cost expected by `end` at the current cost per token (equals `costUsd` for closed blocks). */
|
|
67
|
+
projectedCostUsd: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ComputeBillingBlocksOptions {
|
|
70
|
+
/** Clock used for activity and projections (default `Date.now()`). */
|
|
71
|
+
now?: Date | number;
|
|
72
|
+
/** Block length (default five hours). */
|
|
73
|
+
blockDurationMs?: number;
|
|
74
|
+
/** Align each block's start down to the UTC hour (default true, as ccusage does). */
|
|
75
|
+
alignToHour?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Group usage events into billing blocks, oldest first.
|
|
79
|
+
*
|
|
80
|
+
* Events with an unparseable timestamp are ignored. Events are sorted before
|
|
81
|
+
* grouping, so callers may pass them in any order.
|
|
82
|
+
*/
|
|
83
|
+
export declare function computeBillingBlocks(events: readonly BillingBlockInput[], options?: ComputeBillingBlocksOptions): BillingBlock[];
|
|
84
|
+
/** The block that is still open at the calculator's `now`, if any. */
|
|
85
|
+
export declare function findActiveBillingBlock(blocks: readonly BillingBlock[]): BillingBlock | null;
|