@planu/cli 0.69.0 → 0.71.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/dist/config/license-plans.json +1 -0
- package/dist/engine/token-optimizer/analytics.d.ts +10 -0
- package/dist/engine/token-optimizer/analytics.d.ts.map +1 -0
- package/dist/engine/token-optimizer/analytics.js +362 -0
- package/dist/engine/token-optimizer/analytics.js.map +1 -0
- package/dist/engine/token-optimizer/index.d.ts +2 -0
- package/dist/engine/token-optimizer/index.d.ts.map +1 -1
- package/dist/engine/token-optimizer/index.js +1 -0
- package/dist/engine/token-optimizer/index.js.map +1 -1
- package/dist/engine/token-optimizer/reconciler.d.ts +22 -0
- package/dist/engine/token-optimizer/reconciler.d.ts.map +1 -0
- package/dist/engine/token-optimizer/reconciler.js +215 -0
- package/dist/engine/token-optimizer/reconciler.js.map +1 -0
- package/dist/storage/token-ledger-store.d.ts +26 -0
- package/dist/storage/token-ledger-store.d.ts.map +1 -0
- package/dist/storage/token-ledger-store.js +354 -0
- package/dist/storage/token-ledger-store.js.map +1 -0
- package/dist/tools/register-token-tools.d.ts.map +1 -1
- package/dist/tools/register-token-tools.js +21 -0
- package/dist/tools/register-token-tools.js.map +1 -1
- package/dist/tools/schemas/index.d.ts +1 -0
- package/dist/tools/schemas/index.d.ts.map +1 -1
- package/dist/tools/schemas/index.js +1 -0
- package/dist/tools/schemas/index.js.map +1 -1
- package/dist/tools/schemas/token-intelligence.d.ts +21 -0
- package/dist/tools/schemas/token-intelligence.d.ts.map +1 -0
- package/dist/tools/schemas/token-intelligence.js +18 -0
- package/dist/tools/schemas/token-intelligence.js.map +1 -0
- package/dist/tools/token-intelligence-handler.d.ts +8 -0
- package/dist/tools/token-intelligence-handler.d.ts.map +1 -0
- package/dist/tools/token-intelligence-handler.js +413 -0
- package/dist/tools/token-intelligence-handler.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/token-ledger.d.ts +195 -0
- package/dist/types/token-ledger.d.ts.map +1 -0
- package/dist/types/token-ledger.js +3 -0
- package/dist/types/token-ledger.js.map +1 -0
- package/package.json +1 -1
- package/src/config/license-plans.json +1 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Estimation Reconciler (SPEC-182)
|
|
2
|
+
// Closes the loop: estimate_ai_cost prediction vs actual token usage from ledger
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Constants
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const ACCURATE_THRESHOLD_PCT = 15;
|
|
7
|
+
const GRADE_EXCELLENT = 90;
|
|
8
|
+
const GRADE_GOOD = 75;
|
|
9
|
+
const GRADE_FAIR = 50;
|
|
10
|
+
const CALIBRATION_RECENT_WEIGHT = 2;
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Core functions
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Aggregates ledger entries into actual token/cost totals.
|
|
16
|
+
*/
|
|
17
|
+
function aggregateEntries(entries) {
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
inputTokens: 0,
|
|
21
|
+
outputTokens: 0,
|
|
22
|
+
totalTokens: 0,
|
|
23
|
+
costUsd: 0,
|
|
24
|
+
models: [],
|
|
25
|
+
callCount: 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
let inputTokens = 0;
|
|
29
|
+
let outputTokens = 0;
|
|
30
|
+
let costUsd = 0;
|
|
31
|
+
const modelSet = new Set();
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
inputTokens += entry.inputTokens;
|
|
34
|
+
outputTokens += entry.outputTokens;
|
|
35
|
+
costUsd += entry.costUsd;
|
|
36
|
+
modelSet.add(entry.model);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
inputTokens,
|
|
40
|
+
outputTokens,
|
|
41
|
+
totalTokens: inputTokens + outputTokens,
|
|
42
|
+
costUsd,
|
|
43
|
+
models: [...modelSet],
|
|
44
|
+
callCount: entries.length,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Computes deviation percent: (estimated - actual) / actual * 100.
|
|
49
|
+
* Returns 0 when actual is 0 to avoid division by zero.
|
|
50
|
+
*/
|
|
51
|
+
function computeDeviationPercent(estimated, actual) {
|
|
52
|
+
if (actual === 0) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
return ((estimated - actual) / actual) * 100;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Converts deviation percent to accuracy: max(0, 100 - abs(deviation)).
|
|
59
|
+
*/
|
|
60
|
+
function deviationToAccuracy(deviationPct) {
|
|
61
|
+
return Math.max(0, 100 - Math.abs(deviationPct));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Maps accuracy value to a grade label.
|
|
65
|
+
*/
|
|
66
|
+
function gradeAccuracy(accuracy) {
|
|
67
|
+
if (accuracy >= GRADE_EXCELLENT) {
|
|
68
|
+
return 'excellent';
|
|
69
|
+
}
|
|
70
|
+
if (accuracy >= GRADE_GOOD) {
|
|
71
|
+
return 'good';
|
|
72
|
+
}
|
|
73
|
+
if (accuracy >= GRADE_FAIR) {
|
|
74
|
+
return 'fair';
|
|
75
|
+
}
|
|
76
|
+
return 'poor';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Maps deviation percent to a direction label.
|
|
80
|
+
*/
|
|
81
|
+
function computeDirection(deviationPct) {
|
|
82
|
+
if (Math.abs(deviationPct) <= ACCURATE_THRESHOLD_PCT) {
|
|
83
|
+
return 'accurate';
|
|
84
|
+
}
|
|
85
|
+
return deviationPct > 0 ? 'over' : 'under';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generates a human-readable calibration suggestion from deviation info.
|
|
89
|
+
*/
|
|
90
|
+
function buildCalibrationSuggestion(direction, tokenDeviationPct) {
|
|
91
|
+
if (direction === 'accurate') {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const absPct = Math.abs(tokenDeviationPct).toFixed(1);
|
|
95
|
+
if (direction === 'over') {
|
|
96
|
+
return (`Estimates are running ${absPct}% high. ` +
|
|
97
|
+
`Consider reducing future token estimates by ~${absPct}% for specs of similar size.`);
|
|
98
|
+
}
|
|
99
|
+
return (`Estimates are running ${absPct}% low. ` +
|
|
100
|
+
`Consider increasing future token estimates by ~${absPct}% for specs of similar size.`);
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Exported functions
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
/**
|
|
106
|
+
* Pure reconciliation: compares a pre-implementation estimate against actual
|
|
107
|
+
* ledger entries to produce deviation, accuracy, and calibration data.
|
|
108
|
+
*/
|
|
109
|
+
export function reconcile(input) {
|
|
110
|
+
const estimatedTotal = input.estimatedInputTokens + input.estimatedOutputTokens;
|
|
111
|
+
const actual = aggregateEntries(input.actualEntries);
|
|
112
|
+
const tokenDeviationPct = computeDeviationPercent(estimatedTotal, actual.totalTokens);
|
|
113
|
+
const costDeviationPct = computeDeviationPercent(input.estimatedCostUsd, actual.costUsd);
|
|
114
|
+
const tokenAccuracy = deviationToAccuracy(tokenDeviationPct);
|
|
115
|
+
const costAccuracy = deviationToAccuracy(costDeviationPct);
|
|
116
|
+
const direction = computeDirection(tokenDeviationPct);
|
|
117
|
+
return {
|
|
118
|
+
specId: input.specId,
|
|
119
|
+
estimated: {
|
|
120
|
+
inputTokens: input.estimatedInputTokens,
|
|
121
|
+
outputTokens: input.estimatedOutputTokens,
|
|
122
|
+
totalTokens: estimatedTotal,
|
|
123
|
+
costUsd: input.estimatedCostUsd,
|
|
124
|
+
model: input.estimatedModel,
|
|
125
|
+
},
|
|
126
|
+
actual,
|
|
127
|
+
deviation: {
|
|
128
|
+
tokenPercent: tokenDeviationPct,
|
|
129
|
+
costPercent: costDeviationPct,
|
|
130
|
+
absoluteTokenDiff: estimatedTotal - actual.totalTokens,
|
|
131
|
+
absoluteCostDiff: input.estimatedCostUsd - actual.costUsd,
|
|
132
|
+
direction,
|
|
133
|
+
},
|
|
134
|
+
accuracy: {
|
|
135
|
+
tokenAccuracy,
|
|
136
|
+
costAccuracy,
|
|
137
|
+
grade: gradeAccuracy(tokenAccuracy),
|
|
138
|
+
},
|
|
139
|
+
calibrationSuggestion: buildCalibrationSuggestion(direction, tokenDeviationPct),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Formats a ReconciliationResult as a markdown report with visual indicators.
|
|
144
|
+
*/
|
|
145
|
+
export function formatReconciliation(result) {
|
|
146
|
+
const gradeIcon = result.accuracy.grade === 'excellent'
|
|
147
|
+
? '✅'
|
|
148
|
+
: result.accuracy.grade === 'good'
|
|
149
|
+
? '✅'
|
|
150
|
+
: result.accuracy.grade === 'fair'
|
|
151
|
+
? '⚠️'
|
|
152
|
+
: '❌';
|
|
153
|
+
const directionLabel = result.deviation.direction === 'accurate'
|
|
154
|
+
? '✅ Within 15% (accurate)'
|
|
155
|
+
: result.deviation.direction === 'over'
|
|
156
|
+
? `⚠️ Over-estimated by ${result.deviation.tokenPercent.toFixed(1)}%`
|
|
157
|
+
: `❌ Under-estimated by ${Math.abs(result.deviation.tokenPercent).toFixed(1)}%`;
|
|
158
|
+
const lines = [
|
|
159
|
+
`## Estimation Reconciliation — ${result.specId}`,
|
|
160
|
+
'',
|
|
161
|
+
'### Token Summary',
|
|
162
|
+
`| Metric | Estimated | Actual | Deviation |`,
|
|
163
|
+
`|--------|-----------|--------|-----------|`,
|
|
164
|
+
`| Input tokens | ${String(result.estimated.inputTokens)} | ${String(result.actual.inputTokens)} | — |`,
|
|
165
|
+
`| Output tokens | ${String(result.estimated.outputTokens)} | ${String(result.actual.outputTokens)} | — |`,
|
|
166
|
+
`| Total tokens | ${String(result.estimated.totalTokens)} | ${String(result.actual.totalTokens)} | ${result.deviation.tokenPercent.toFixed(1)}% |`,
|
|
167
|
+
`| Cost (USD) | $${result.estimated.costUsd.toFixed(6)} | $${result.actual.costUsd.toFixed(6)} | ${result.deviation.costPercent.toFixed(1)}% |`,
|
|
168
|
+
'',
|
|
169
|
+
'### Accuracy',
|
|
170
|
+
`${gradeIcon} Grade: **${result.accuracy.grade.toUpperCase()}**`,
|
|
171
|
+
`- Token accuracy: ${result.accuracy.tokenAccuracy.toFixed(1)}%`,
|
|
172
|
+
`- Cost accuracy: ${result.accuracy.costAccuracy.toFixed(1)}%`,
|
|
173
|
+
`- Direction: ${directionLabel}`,
|
|
174
|
+
'',
|
|
175
|
+
'### Actual Usage Details',
|
|
176
|
+
`- API calls: ${String(result.actual.callCount)}`,
|
|
177
|
+
`- Models used: ${result.actual.models.join(', ') || '—'}`,
|
|
178
|
+
];
|
|
179
|
+
if (result.calibrationSuggestion) {
|
|
180
|
+
lines.push('', '### Calibration Suggestion', result.calibrationSuggestion);
|
|
181
|
+
}
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Extracts calibration data points from multiple reconciliation results.
|
|
186
|
+
*/
|
|
187
|
+
export function buildCalibrationData(results) {
|
|
188
|
+
return results.map((r) => ({
|
|
189
|
+
specId: r.specId,
|
|
190
|
+
timestamp: new Date().toISOString(),
|
|
191
|
+
estimatedTokens: r.estimated.totalTokens,
|
|
192
|
+
actualTokens: r.actual.totalTokens,
|
|
193
|
+
ratio: r.estimated.totalTokens > 0 ? r.actual.totalTokens / r.estimated.totalTokens : 1,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Computes a weighted average calibration factor from historical data.
|
|
198
|
+
* Recent entries (second half of the array) are weighted 2x.
|
|
199
|
+
* Returns the factor to multiply future estimates by (>1 = need to increase estimates).
|
|
200
|
+
*/
|
|
201
|
+
export function suggestCalibrationFactor(calibrations) {
|
|
202
|
+
if (calibrations.length === 0) {
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
const midpoint = Math.floor(calibrations.length / 2);
|
|
206
|
+
let weightedSum = 0;
|
|
207
|
+
let totalWeight = 0;
|
|
208
|
+
for (let i = 0; i < calibrations.length; i++) {
|
|
209
|
+
const weight = i >= midpoint ? CALIBRATION_RECENT_WEIGHT : 1;
|
|
210
|
+
weightedSum += (calibrations[i]?.ratio ?? 1) * weight;
|
|
211
|
+
totalWeight += weight;
|
|
212
|
+
}
|
|
213
|
+
return weightedSum / totalWeight;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=reconciler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.js","sourceRoot":"","sources":["../../../src/engine/token-optimizer/reconciler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LedgerEntry, LedgerFilter, LedgerAggregation, MonthlyLedger } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Append a new entry to the current month's ledger file.
|
|
4
|
+
*/
|
|
5
|
+
export declare function recordEntry(projectHash: string, dataDir: string, entry: LedgerEntry): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Load and filter ledger entries according to the given filter criteria.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getEntries(projectHash: string, dataDir: string, filter: LedgerFilter): Promise<LedgerEntry[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Compute aggregated statistics for filtered ledger entries.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAggregation(projectHash: string, dataDir: string, filter: LedgerFilter): Promise<LedgerAggregation>;
|
|
14
|
+
/**
|
|
15
|
+
* Load the current month's ledger including recomputed aggregation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCurrentMonthLedger(projectHash: string, dataDir: string): Promise<MonthlyLedger>;
|
|
18
|
+
/**
|
|
19
|
+
* Return the list of months for which ledger files exist (YYYY-MM format).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getHistoricalMonths(projectHash: string, dataDir: string): Promise<string[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Archive months older than ROTATION_MONTHS into a summary.json file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rotateLedger(projectHash: string, dataDir: string): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=token-ledger-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-ledger-store.d.ts","sourceRoot":"","sources":["../../src/storage/token-ledger-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAwR3B;;GAEG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,WAAW,EAAE,CAAC,CA2BxB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CASxB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE3F;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCtF"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// storage/token-ledger-store.ts — Token usage ledger persistence (SPEC-182)
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { withFileLock } from './file-mutex.js';
|
|
5
|
+
const MAX_FILE_SIZE_MB = 10;
|
|
6
|
+
const ROTATION_MONTHS = 6;
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Path helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
function getLedgerDir(projectHash, dataDir) {
|
|
11
|
+
return join(dataDir, 'projects', projectHash, 'token-ledger');
|
|
12
|
+
}
|
|
13
|
+
function getLedgerPath(projectHash, dataDir, month) {
|
|
14
|
+
return join(getLedgerDir(projectHash, dataDir), `${month}.json`);
|
|
15
|
+
}
|
|
16
|
+
function getSummaryPath(projectHash, dataDir) {
|
|
17
|
+
return join(getLedgerDir(projectHash, dataDir), 'summary.json');
|
|
18
|
+
}
|
|
19
|
+
function currentMonth() {
|
|
20
|
+
return new Date().toISOString().slice(0, 7); // YYYY-MM
|
|
21
|
+
}
|
|
22
|
+
function ensureDir(dirPath) {
|
|
23
|
+
if (!existsSync(dirPath)) {
|
|
24
|
+
mkdirSync(dirPath, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Date range helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
function getStartOfToday() {
|
|
31
|
+
const d = new Date();
|
|
32
|
+
d.setHours(0, 0, 0, 0);
|
|
33
|
+
return d.toISOString();
|
|
34
|
+
}
|
|
35
|
+
function getStartOfWeek() {
|
|
36
|
+
const d = new Date();
|
|
37
|
+
d.setDate(d.getDate() - 6);
|
|
38
|
+
d.setHours(0, 0, 0, 0);
|
|
39
|
+
return d.toISOString();
|
|
40
|
+
}
|
|
41
|
+
function getStartOfMonth() {
|
|
42
|
+
const d = new Date();
|
|
43
|
+
d.setDate(1);
|
|
44
|
+
d.setHours(0, 0, 0, 0);
|
|
45
|
+
return d.toISOString();
|
|
46
|
+
}
|
|
47
|
+
function filterByPeriod(entries, period) {
|
|
48
|
+
if (!period || period === 'all_time') {
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
let cutoff;
|
|
52
|
+
if (period === 'today') {
|
|
53
|
+
cutoff = getStartOfToday();
|
|
54
|
+
}
|
|
55
|
+
else if (period === 'week') {
|
|
56
|
+
cutoff = getStartOfWeek();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
cutoff = getStartOfMonth();
|
|
60
|
+
}
|
|
61
|
+
return entries.filter((e) => e.timestamp >= cutoff);
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Low-level file I/O
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
function readMonthFile(filePath) {
|
|
67
|
+
if (!existsSync(filePath)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (!Array.isArray(parsed)) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeMonthFile(filePath, entries) {
|
|
83
|
+
ensureDir(dirname(filePath));
|
|
84
|
+
const json = JSON.stringify(entries, null, 2);
|
|
85
|
+
const sizeMb = Buffer.byteLength(json, 'utf-8') / (1024 * 1024);
|
|
86
|
+
if (sizeMb > MAX_FILE_SIZE_MB) {
|
|
87
|
+
// Keep newest entries, drop oldest
|
|
88
|
+
const sorted = [...entries].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
89
|
+
const trimmed = trimToMaxSize(sorted);
|
|
90
|
+
writeFileSync(filePath, JSON.stringify(trimmed, null, 2), 'utf-8');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
writeFileSync(filePath, json, 'utf-8');
|
|
94
|
+
}
|
|
95
|
+
function trimToMaxSize(sorted) {
|
|
96
|
+
const maxBytes = MAX_FILE_SIZE_MB * 1024 * 1024;
|
|
97
|
+
const trimmed = [];
|
|
98
|
+
let totalSize = 0;
|
|
99
|
+
for (const entry of sorted) {
|
|
100
|
+
const entrySize = Buffer.byteLength(JSON.stringify(entry), 'utf-8');
|
|
101
|
+
if (totalSize + entrySize > maxBytes) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
trimmed.push(entry);
|
|
105
|
+
totalSize += entrySize;
|
|
106
|
+
}
|
|
107
|
+
return trimmed;
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Aggregation computation
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
function computeAggregation(entries) {
|
|
113
|
+
if (entries.length === 0) {
|
|
114
|
+
return emptyAggregation();
|
|
115
|
+
}
|
|
116
|
+
const tokensByTool = {};
|
|
117
|
+
const costByTool = {};
|
|
118
|
+
const tokensByModel = {};
|
|
119
|
+
const costByModel = {};
|
|
120
|
+
const tools = new Set();
|
|
121
|
+
const models = new Set();
|
|
122
|
+
const sessions = new Set();
|
|
123
|
+
let totalTokens = 0;
|
|
124
|
+
let totalInputTokens = 0;
|
|
125
|
+
let totalOutputTokens = 0;
|
|
126
|
+
let totalCostUsd = 0;
|
|
127
|
+
let totalEstimatedCostUsd = 0;
|
|
128
|
+
let totalActualForAccuracy = 0;
|
|
129
|
+
let totalEstimatedForAccuracy = 0;
|
|
130
|
+
let estimationCount = 0;
|
|
131
|
+
for (const e of entries) {
|
|
132
|
+
totalTokens += e.totalTokens;
|
|
133
|
+
totalInputTokens += e.inputTokens;
|
|
134
|
+
totalOutputTokens += e.outputTokens;
|
|
135
|
+
totalCostUsd += e.costUsd;
|
|
136
|
+
if (e.estimatedCostUsd !== undefined) {
|
|
137
|
+
totalEstimatedCostUsd += e.estimatedCostUsd;
|
|
138
|
+
}
|
|
139
|
+
if (e.estimatedTokens !== undefined) {
|
|
140
|
+
totalActualForAccuracy += e.totalTokens;
|
|
141
|
+
totalEstimatedForAccuracy += e.estimatedTokens;
|
|
142
|
+
estimationCount++;
|
|
143
|
+
}
|
|
144
|
+
tools.add(e.toolName);
|
|
145
|
+
models.add(e.model);
|
|
146
|
+
sessions.add(e.sessionId);
|
|
147
|
+
tokensByTool[e.toolName] = (tokensByTool[e.toolName] ?? 0) + e.totalTokens;
|
|
148
|
+
costByTool[e.toolName] = (costByTool[e.toolName] ?? 0) + e.costUsd;
|
|
149
|
+
tokensByModel[e.model] = (tokensByModel[e.model] ?? 0) + e.totalTokens;
|
|
150
|
+
costByModel[e.model] = (costByModel[e.model] ?? 0) + e.costUsd;
|
|
151
|
+
}
|
|
152
|
+
const count = entries.length;
|
|
153
|
+
const estimationAccuracy = computeEstimationAccuracy(totalActualForAccuracy, totalEstimatedForAccuracy, estimationCount);
|
|
154
|
+
const overEstimationPercent = computeOverEstimationPercent(totalActualForAccuracy, totalEstimatedForAccuracy, estimationCount);
|
|
155
|
+
return {
|
|
156
|
+
totalTokens,
|
|
157
|
+
totalInputTokens,
|
|
158
|
+
totalOutputTokens,
|
|
159
|
+
totalCostUsd,
|
|
160
|
+
totalEstimatedCostUsd,
|
|
161
|
+
entryCount: count,
|
|
162
|
+
uniqueTools: tools.size,
|
|
163
|
+
uniqueModels: models.size,
|
|
164
|
+
uniqueSessions: sessions.size,
|
|
165
|
+
avgTokensPerCall: count > 0 ? totalTokens / count : 0,
|
|
166
|
+
avgCostPerCall: count > 0 ? totalCostUsd / count : 0,
|
|
167
|
+
tokensByTool,
|
|
168
|
+
costByTool,
|
|
169
|
+
tokensByModel,
|
|
170
|
+
costByModel,
|
|
171
|
+
estimationAccuracy,
|
|
172
|
+
overEstimationPercent,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function computeEstimationAccuracy(actual, estimated, count) {
|
|
176
|
+
if (count === 0 || actual === 0) {
|
|
177
|
+
return 100;
|
|
178
|
+
}
|
|
179
|
+
const accuracy = 100 - (Math.abs(estimated - actual) / actual) * 100;
|
|
180
|
+
return Math.max(0, Math.min(100, accuracy));
|
|
181
|
+
}
|
|
182
|
+
function computeOverEstimationPercent(actual, estimated, count) {
|
|
183
|
+
if (count === 0 || actual === 0) {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
return ((estimated - actual) / actual) * 100;
|
|
187
|
+
}
|
|
188
|
+
function emptyAggregation() {
|
|
189
|
+
return {
|
|
190
|
+
totalTokens: 0,
|
|
191
|
+
totalInputTokens: 0,
|
|
192
|
+
totalOutputTokens: 0,
|
|
193
|
+
totalCostUsd: 0,
|
|
194
|
+
totalEstimatedCostUsd: 0,
|
|
195
|
+
entryCount: 0,
|
|
196
|
+
uniqueTools: 0,
|
|
197
|
+
uniqueModels: 0,
|
|
198
|
+
uniqueSessions: 0,
|
|
199
|
+
avgTokensPerCall: 0,
|
|
200
|
+
avgCostPerCall: 0,
|
|
201
|
+
tokensByTool: {},
|
|
202
|
+
costByTool: {},
|
|
203
|
+
tokensByModel: {},
|
|
204
|
+
costByModel: {},
|
|
205
|
+
estimationAccuracy: 100,
|
|
206
|
+
overEstimationPercent: 0,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Multi-month entry loading (for all_time filter)
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
function loadAllMonthEntries(projectHash, dataDir) {
|
|
213
|
+
const months = listAvailableMonths(projectHash, dataDir);
|
|
214
|
+
const all = [];
|
|
215
|
+
for (const month of months) {
|
|
216
|
+
const filePath = getLedgerPath(projectHash, dataDir, month);
|
|
217
|
+
all.push(...readMonthFile(filePath));
|
|
218
|
+
}
|
|
219
|
+
return all;
|
|
220
|
+
}
|
|
221
|
+
function listAvailableMonths(projectHash, dataDir) {
|
|
222
|
+
const dir = getLedgerDir(projectHash, dataDir);
|
|
223
|
+
if (!existsSync(dir)) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
return readdirSync(dir)
|
|
228
|
+
.filter((f) => /^\d{4}-\d{2}\.json$/.test(f))
|
|
229
|
+
.map((f) => f.slice(0, 7))
|
|
230
|
+
.sort();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Public API
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
/**
|
|
240
|
+
* Append a new entry to the current month's ledger file.
|
|
241
|
+
*/
|
|
242
|
+
export async function recordEntry(projectHash, dataDir, entry) {
|
|
243
|
+
const month = currentMonth();
|
|
244
|
+
const filePath = getLedgerPath(projectHash, dataDir, month);
|
|
245
|
+
await withFileLock(filePath, () => {
|
|
246
|
+
const entries = readMonthFile(filePath);
|
|
247
|
+
entries.push(entry);
|
|
248
|
+
writeMonthFile(filePath, entries);
|
|
249
|
+
return Promise.resolve();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Load and filter ledger entries according to the given filter criteria.
|
|
254
|
+
*/
|
|
255
|
+
export function getEntries(projectHash, dataDir, filter) {
|
|
256
|
+
let entries;
|
|
257
|
+
if (!filter.period || filter.period === 'all_time') {
|
|
258
|
+
entries = loadAllMonthEntries(projectHash, dataDir);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const month = currentMonth();
|
|
262
|
+
const filePath = getLedgerPath(projectHash, dataDir, month);
|
|
263
|
+
entries = readMonthFile(filePath);
|
|
264
|
+
}
|
|
265
|
+
entries = filterByPeriod(entries, filter.period);
|
|
266
|
+
if (filter.specId) {
|
|
267
|
+
entries = entries.filter((e) => e.specId === filter.specId);
|
|
268
|
+
}
|
|
269
|
+
if (filter.toolName) {
|
|
270
|
+
entries = entries.filter((e) => e.toolName === filter.toolName);
|
|
271
|
+
}
|
|
272
|
+
if (filter.model) {
|
|
273
|
+
entries = entries.filter((e) => e.model === filter.model);
|
|
274
|
+
}
|
|
275
|
+
if (filter.sessionId) {
|
|
276
|
+
entries = entries.filter((e) => e.sessionId === filter.sessionId);
|
|
277
|
+
}
|
|
278
|
+
return Promise.resolve(entries);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Compute aggregated statistics for filtered ledger entries.
|
|
282
|
+
*/
|
|
283
|
+
export async function getAggregation(projectHash, dataDir, filter) {
|
|
284
|
+
const entries = await getEntries(projectHash, dataDir, filter);
|
|
285
|
+
return computeAggregation(entries);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Load the current month's ledger including recomputed aggregation.
|
|
289
|
+
*/
|
|
290
|
+
export function getCurrentMonthLedger(projectHash, dataDir) {
|
|
291
|
+
const month = currentMonth();
|
|
292
|
+
const filePath = getLedgerPath(projectHash, dataDir, month);
|
|
293
|
+
const entries = readMonthFile(filePath);
|
|
294
|
+
return Promise.resolve({
|
|
295
|
+
month,
|
|
296
|
+
entries,
|
|
297
|
+
aggregation: computeAggregation(entries),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Return the list of months for which ledger files exist (YYYY-MM format).
|
|
302
|
+
*/
|
|
303
|
+
export function getHistoricalMonths(projectHash, dataDir) {
|
|
304
|
+
return Promise.resolve(listAvailableMonths(projectHash, dataDir));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Archive months older than ROTATION_MONTHS into a summary.json file.
|
|
308
|
+
*/
|
|
309
|
+
export async function rotateLedger(projectHash, dataDir) {
|
|
310
|
+
const months = listAvailableMonths(projectHash, dataDir);
|
|
311
|
+
const cutoff = new Date();
|
|
312
|
+
cutoff.setMonth(cutoff.getMonth() - ROTATION_MONTHS);
|
|
313
|
+
const cutoffStr = cutoff.toISOString().slice(0, 7);
|
|
314
|
+
const toArchive = months.filter((m) => m < cutoffStr);
|
|
315
|
+
if (toArchive.length === 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const summaryPath = getSummaryPath(projectHash, dataDir);
|
|
319
|
+
await withFileLock(summaryPath, () => {
|
|
320
|
+
const existing = readSummary(summaryPath);
|
|
321
|
+
for (const month of toArchive) {
|
|
322
|
+
const filePath = getLedgerPath(projectHash, dataDir, month);
|
|
323
|
+
const entries = readMonthFile(filePath);
|
|
324
|
+
const agg = computeAggregation(entries);
|
|
325
|
+
existing[month] = agg;
|
|
326
|
+
// Remove the detailed file after archiving
|
|
327
|
+
try {
|
|
328
|
+
writeFileSync(filePath, '[]', 'utf-8');
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// ignore
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
writeFileSync(summaryPath, JSON.stringify(existing, null, 2), 'utf-8');
|
|
335
|
+
return Promise.resolve();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function readSummary(summaryPath) {
|
|
339
|
+
if (!existsSync(summaryPath)) {
|
|
340
|
+
return {};
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const raw = readFileSync(summaryPath, 'utf-8');
|
|
344
|
+
const parsed = JSON.parse(raw);
|
|
345
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
346
|
+
return {};
|
|
347
|
+
}
|
|
348
|
+
return parsed;
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=token-ledger-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-ledger-store.js","sourceRoot":"","sources":["../../src/storage/token-ledger-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-token-tools.d.ts","sourceRoot":"","sources":["../../src/tools/register-token-tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"register-token-tools.d.ts","sourceRoot":"","sources":["../../src/tools/register-token-tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqE1D"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// tools/register-token-tools.ts — Registers token optimization tools on the MCP server (SPEC-082)
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { safe } from './safe-handler.js';
|
|
3
4
|
import { handleTokenUsage } from './token-usage-handler.js';
|
|
5
|
+
import { handleTokenIntelligence } from './token-intelligence-handler.js';
|
|
4
6
|
import { TokenUsagePeriodEnum, TokenUsageGroupByEnum } from './schemas/token-optimization.js';
|
|
7
|
+
import { TokenIntelligencePeriodEnum, TokenIntelligenceGroupByEnum, TokenIntelligenceViewEnum, } from './schemas/token-intelligence.js';
|
|
5
8
|
export function registerTokenTools(server) {
|
|
6
9
|
server.registerTool('token_usage', {
|
|
7
10
|
description: 'View token consumption metrics, cache performance, and optimization savings. ' +
|
|
@@ -13,5 +16,23 @@ export function registerTokenTools(server) {
|
|
|
13
16
|
},
|
|
14
17
|
annotations: { title: 'Token Usage', readOnlyHint: true },
|
|
15
18
|
}, safe(async (args) => handleTokenUsage(args)));
|
|
19
|
+
server.registerTool('token_intelligence', {
|
|
20
|
+
description: 'Advanced token analytics dashboard with cost tracking, budget monitoring, estimation ' +
|
|
21
|
+
'accuracy, trend detection, and anomaly alerts. Shows real spending data persisted across sessions.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
period: TokenIntelligencePeriodEnum.optional().describe('Time period filter. Type: today | week | month | all_time. Defaults to month.'),
|
|
24
|
+
groupBy: TokenIntelligenceGroupByEnum.optional().describe('Group breakdown by: tool | model | spec | day. Defaults to tool.'),
|
|
25
|
+
view: TokenIntelligenceViewEnum.optional().describe('Dashboard view: summary | detailed | budget | reconciliation | trends. Defaults to summary.'),
|
|
26
|
+
specId: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Filter by a specific spec ID (e.g. SPEC-180). Shows only ledger entries tagged to that spec.'),
|
|
30
|
+
projectPath: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Absolute path to the project root. Defaults to current working directory.'),
|
|
34
|
+
},
|
|
35
|
+
annotations: { title: 'Token Intelligence', readOnlyHint: true },
|
|
36
|
+
}, safe(async (args) => handleTokenIntelligence(args)));
|
|
16
37
|
}
|
|
17
38
|
//# sourceMappingURL=register-token-tools.js.map
|