tlc-claude-code 1.4.9 → 1.5.2
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/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Reports Tests
|
|
3
|
+
*
|
|
4
|
+
* Generate cost reports by various dimensions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
generateReport,
|
|
12
|
+
filterByPeriod,
|
|
13
|
+
groupByModel,
|
|
14
|
+
groupByOperation,
|
|
15
|
+
groupByTrigger,
|
|
16
|
+
exportCSV,
|
|
17
|
+
exportJSON,
|
|
18
|
+
analyzeTrends,
|
|
19
|
+
compareToLastPeriod,
|
|
20
|
+
formatReport,
|
|
21
|
+
} = require('./cost-reports.js');
|
|
22
|
+
|
|
23
|
+
describe('Cost Reports', () => {
|
|
24
|
+
const sampleData = [
|
|
25
|
+
{ date: '2025-01-15', model: 'claude-3-opus', operation: 'code-gen', trigger: 'manual', cost: 1.00 },
|
|
26
|
+
{ date: '2025-01-15', model: 'gpt-4', operation: 'review', trigger: 'ci', cost: 0.50 },
|
|
27
|
+
{ date: '2025-01-16', model: 'claude-3-opus', operation: 'code-gen', trigger: 'manual', cost: 1.50 },
|
|
28
|
+
{ date: '2025-01-16', model: 'claude-3-haiku', operation: 'chat', trigger: 'webhook', cost: 0.10 },
|
|
29
|
+
{ date: '2025-01-17', model: 'gpt-4', operation: 'review', trigger: 'ci', cost: 0.75 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
describe('generateReport', () => {
|
|
33
|
+
it('creates report object', () => {
|
|
34
|
+
const report = generateReport(sampleData);
|
|
35
|
+
|
|
36
|
+
assert.ok(report);
|
|
37
|
+
assert.ok(report.totalCost !== undefined);
|
|
38
|
+
assert.ok(report.recordCount !== undefined);
|
|
39
|
+
assert.ok(report.dateRange);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('calculates total cost', () => {
|
|
43
|
+
const report = generateReport(sampleData);
|
|
44
|
+
|
|
45
|
+
assert.strictEqual(report.totalCost, 3.85);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('filterByPeriod', () => {
|
|
50
|
+
it('selects date range', () => {
|
|
51
|
+
const filtered = filterByPeriod(sampleData, {
|
|
52
|
+
startDate: '2025-01-15',
|
|
53
|
+
endDate: '2025-01-16',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
assert.strictEqual(filtered.length, 4);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('handles single day', () => {
|
|
60
|
+
const filtered = filterByPeriod(sampleData, {
|
|
61
|
+
startDate: '2025-01-15',
|
|
62
|
+
endDate: '2025-01-15',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
assert.strictEqual(filtered.length, 2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns empty for no matches', () => {
|
|
69
|
+
const filtered = filterByPeriod(sampleData, {
|
|
70
|
+
startDate: '2024-01-01',
|
|
71
|
+
endDate: '2024-01-31',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(filtered.length, 0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('groupByModel', () => {
|
|
79
|
+
it('aggregates costs by model', () => {
|
|
80
|
+
const grouped = groupByModel(sampleData);
|
|
81
|
+
|
|
82
|
+
assert.ok(grouped['claude-3-opus']);
|
|
83
|
+
assert.ok(grouped['gpt-4']);
|
|
84
|
+
assert.ok(grouped['claude-3-haiku']);
|
|
85
|
+
|
|
86
|
+
assert.strictEqual(grouped['claude-3-opus'], 2.50);
|
|
87
|
+
assert.strictEqual(grouped['gpt-4'], 1.25);
|
|
88
|
+
assert.strictEqual(grouped['claude-3-haiku'], 0.10);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('groupByOperation', () => {
|
|
93
|
+
it('aggregates costs by operation', () => {
|
|
94
|
+
const grouped = groupByOperation(sampleData);
|
|
95
|
+
|
|
96
|
+
assert.ok(grouped['code-gen']);
|
|
97
|
+
assert.ok(grouped['review']);
|
|
98
|
+
assert.ok(grouped['chat']);
|
|
99
|
+
|
|
100
|
+
assert.strictEqual(grouped['code-gen'], 2.50);
|
|
101
|
+
assert.strictEqual(grouped['review'], 1.25);
|
|
102
|
+
assert.strictEqual(grouped['chat'], 0.10);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('groupByTrigger', () => {
|
|
107
|
+
it('aggregates costs by trigger', () => {
|
|
108
|
+
const grouped = groupByTrigger(sampleData);
|
|
109
|
+
|
|
110
|
+
assert.ok(grouped['manual']);
|
|
111
|
+
assert.ok(grouped['ci']);
|
|
112
|
+
assert.ok(grouped['webhook']);
|
|
113
|
+
|
|
114
|
+
assert.strictEqual(grouped['manual'], 2.50);
|
|
115
|
+
assert.strictEqual(grouped['ci'], 1.25);
|
|
116
|
+
assert.strictEqual(grouped['webhook'], 0.10);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('exportCSV', () => {
|
|
121
|
+
it('formats correctly', () => {
|
|
122
|
+
const csv = exportCSV(sampleData);
|
|
123
|
+
|
|
124
|
+
assert.ok(typeof csv === 'string');
|
|
125
|
+
assert.ok(csv.includes('date'));
|
|
126
|
+
assert.ok(csv.includes('model'));
|
|
127
|
+
assert.ok(csv.includes('cost'));
|
|
128
|
+
assert.ok(csv.includes('claude-3-opus'));
|
|
129
|
+
|
|
130
|
+
// Should have header + 5 data rows
|
|
131
|
+
const lines = csv.trim().split('\n');
|
|
132
|
+
assert.strictEqual(lines.length, 6);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('exportJSON', () => {
|
|
137
|
+
it('formats correctly', () => {
|
|
138
|
+
const json = exportJSON(sampleData);
|
|
139
|
+
|
|
140
|
+
assert.ok(typeof json === 'string');
|
|
141
|
+
|
|
142
|
+
const parsed = JSON.parse(json);
|
|
143
|
+
assert.ok(Array.isArray(parsed));
|
|
144
|
+
assert.strictEqual(parsed.length, 5);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('includes all fields', () => {
|
|
148
|
+
const json = exportJSON(sampleData);
|
|
149
|
+
const parsed = JSON.parse(json);
|
|
150
|
+
|
|
151
|
+
assert.ok(parsed[0].date);
|
|
152
|
+
assert.ok(parsed[0].model);
|
|
153
|
+
assert.ok(parsed[0].operation);
|
|
154
|
+
assert.ok(parsed[0].trigger);
|
|
155
|
+
assert.ok(parsed[0].cost !== undefined);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('analyzeTrends', () => {
|
|
160
|
+
it('shows direction increasing', () => {
|
|
161
|
+
const data = [
|
|
162
|
+
{ date: '2025-01-01', cost: 1.00 },
|
|
163
|
+
{ date: '2025-01-02', cost: 1.50 },
|
|
164
|
+
{ date: '2025-01-03', cost: 2.00 },
|
|
165
|
+
{ date: '2025-01-04', cost: 2.50 },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
const trend = analyzeTrends(data);
|
|
169
|
+
|
|
170
|
+
assert.strictEqual(trend.direction, 'increasing');
|
|
171
|
+
assert.ok(trend.percentChange > 0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('shows direction decreasing', () => {
|
|
175
|
+
const data = [
|
|
176
|
+
{ date: '2025-01-01', cost: 2.50 },
|
|
177
|
+
{ date: '2025-01-02', cost: 2.00 },
|
|
178
|
+
{ date: '2025-01-03', cost: 1.50 },
|
|
179
|
+
{ date: '2025-01-04', cost: 1.00 },
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const trend = analyzeTrends(data);
|
|
183
|
+
|
|
184
|
+
assert.strictEqual(trend.direction, 'decreasing');
|
|
185
|
+
assert.ok(trend.percentChange < 0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('shows direction stable', () => {
|
|
189
|
+
const data = [
|
|
190
|
+
{ date: '2025-01-01', cost: 1.00 },
|
|
191
|
+
{ date: '2025-01-02', cost: 1.00 },
|
|
192
|
+
{ date: '2025-01-03', cost: 1.00 },
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const trend = analyzeTrends(data);
|
|
196
|
+
|
|
197
|
+
assert.strictEqual(trend.direction, 'stable');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('compareToLastPeriod', () => {
|
|
202
|
+
it('calculates difference', () => {
|
|
203
|
+
const currentPeriod = [
|
|
204
|
+
{ cost: 5.00 },
|
|
205
|
+
{ cost: 3.00 },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
const lastPeriod = [
|
|
209
|
+
{ cost: 4.00 },
|
|
210
|
+
{ cost: 2.00 },
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const comparison = compareToLastPeriod(currentPeriod, lastPeriod);
|
|
214
|
+
|
|
215
|
+
assert.strictEqual(comparison.currentTotal, 8.00);
|
|
216
|
+
assert.strictEqual(comparison.lastTotal, 6.00);
|
|
217
|
+
assert.strictEqual(comparison.difference, 2.00);
|
|
218
|
+
assert.ok(comparison.percentChange > 0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('handles empty last period', () => {
|
|
222
|
+
const currentPeriod = [{ cost: 5.00 }];
|
|
223
|
+
const lastPeriod = [];
|
|
224
|
+
|
|
225
|
+
const comparison = compareToLastPeriod(currentPeriod, lastPeriod);
|
|
226
|
+
|
|
227
|
+
assert.strictEqual(comparison.currentTotal, 5.00);
|
|
228
|
+
assert.strictEqual(comparison.lastTotal, 0);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('formatReport', () => {
|
|
233
|
+
it('creates markdown report', () => {
|
|
234
|
+
const report = generateReport(sampleData);
|
|
235
|
+
const formatted = formatReport(report);
|
|
236
|
+
|
|
237
|
+
assert.ok(typeof formatted === 'string');
|
|
238
|
+
assert.ok(formatted.includes('#')); // Markdown headers
|
|
239
|
+
assert.ok(formatted.includes('Total'));
|
|
240
|
+
assert.ok(formatted.includes('$'));
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('includes breakdown sections', () => {
|
|
244
|
+
const report = generateReport(sampleData);
|
|
245
|
+
report.byModel = groupByModel(sampleData);
|
|
246
|
+
report.byOperation = groupByOperation(sampleData);
|
|
247
|
+
|
|
248
|
+
const formatted = formatReport(report);
|
|
249
|
+
|
|
250
|
+
assert.ok(formatted.includes('Model'));
|
|
251
|
+
assert.ok(formatted.includes('Operation'));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Tracker Module
|
|
3
|
+
*
|
|
4
|
+
* Real-time cost tracking per agent, session, and day
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a new cost tracker instance
|
|
12
|
+
* @param {Object} options - Configuration options
|
|
13
|
+
* @param {Object} options.fs - File system module (for testing)
|
|
14
|
+
* @returns {Object} Cost tracker instance
|
|
15
|
+
*/
|
|
16
|
+
function createCostTracker(options = {}) {
|
|
17
|
+
const fsModule = options.fs || fs;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
records: [],
|
|
21
|
+
byAgent: {},
|
|
22
|
+
bySession: {},
|
|
23
|
+
byDay: {},
|
|
24
|
+
byModel: {},
|
|
25
|
+
byProvider: {},
|
|
26
|
+
fs: fsModule,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Record a cost entry
|
|
32
|
+
* @param {Object} tracker - Cost tracker instance
|
|
33
|
+
* @param {Object} entry - Cost entry
|
|
34
|
+
* @param {string} entry.agentId - Agent identifier
|
|
35
|
+
* @param {string} entry.sessionId - Session identifier
|
|
36
|
+
* @param {string} entry.model - Model name
|
|
37
|
+
* @param {string} entry.provider - Provider name
|
|
38
|
+
* @param {number} entry.cost - Cost in dollars
|
|
39
|
+
* @param {number} [entry.inputTokens] - Input token count
|
|
40
|
+
* @param {number} [entry.outputTokens] - Output token count
|
|
41
|
+
* @param {string} [entry.timestamp] - ISO timestamp
|
|
42
|
+
*/
|
|
43
|
+
function recordCost(tracker, entry) {
|
|
44
|
+
const timestamp = entry.timestamp || new Date().toISOString();
|
|
45
|
+
const day = timestamp.split('T')[0];
|
|
46
|
+
|
|
47
|
+
const record = {
|
|
48
|
+
...entry,
|
|
49
|
+
timestamp,
|
|
50
|
+
day,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
tracker.records.push(record);
|
|
54
|
+
|
|
55
|
+
// Aggregate by agent
|
|
56
|
+
tracker.byAgent[entry.agentId] = (tracker.byAgent[entry.agentId] || 0) + entry.cost;
|
|
57
|
+
|
|
58
|
+
// Aggregate by session
|
|
59
|
+
tracker.bySession[entry.sessionId] = (tracker.bySession[entry.sessionId] || 0) + entry.cost;
|
|
60
|
+
|
|
61
|
+
// Aggregate by day
|
|
62
|
+
tracker.byDay[day] = (tracker.byDay[day] || 0) + entry.cost;
|
|
63
|
+
|
|
64
|
+
// Aggregate by model
|
|
65
|
+
tracker.byModel[entry.model] = (tracker.byModel[entry.model] || 0) + entry.cost;
|
|
66
|
+
|
|
67
|
+
// Aggregate by provider
|
|
68
|
+
tracker.byProvider[entry.provider] = (tracker.byProvider[entry.provider] || 0) + entry.cost;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get cost for a specific agent
|
|
73
|
+
* @param {Object} tracker - Cost tracker instance
|
|
74
|
+
* @param {string} agentId - Agent identifier
|
|
75
|
+
* @returns {number} Total cost for agent
|
|
76
|
+
*/
|
|
77
|
+
function getAgentCost(tracker, agentId) {
|
|
78
|
+
return tracker.byAgent[agentId] || 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get cost for a specific session
|
|
83
|
+
* @param {Object} tracker - Cost tracker instance
|
|
84
|
+
* @param {string} sessionId - Session identifier
|
|
85
|
+
* @returns {number} Total cost for session
|
|
86
|
+
*/
|
|
87
|
+
function getSessionCost(tracker, sessionId) {
|
|
88
|
+
return tracker.bySession[sessionId] || 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get cost for a specific day
|
|
93
|
+
* @param {Object} tracker - Cost tracker instance
|
|
94
|
+
* @param {string} day - Day in YYYY-MM-DD format
|
|
95
|
+
* @returns {number} Total cost for day
|
|
96
|
+
*/
|
|
97
|
+
function getDailyCost(tracker, day) {
|
|
98
|
+
return tracker.byDay[day] || 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get cost for current week
|
|
103
|
+
* @param {Object} tracker - Cost tracker instance
|
|
104
|
+
* @returns {number} Total cost for current week
|
|
105
|
+
*/
|
|
106
|
+
function getWeeklyCost(tracker) {
|
|
107
|
+
const now = new Date();
|
|
108
|
+
const weekStart = new Date(now);
|
|
109
|
+
weekStart.setDate(now.getDate() - now.getDay());
|
|
110
|
+
weekStart.setHours(0, 0, 0, 0);
|
|
111
|
+
|
|
112
|
+
let total = 0;
|
|
113
|
+
for (const [day, cost] of Object.entries(tracker.byDay)) {
|
|
114
|
+
const dayDate = new Date(day);
|
|
115
|
+
if (dayDate >= weekStart && dayDate <= now) {
|
|
116
|
+
total += cost;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return total;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get cost for current month
|
|
125
|
+
* @param {Object} tracker - Cost tracker instance
|
|
126
|
+
* @returns {number} Total cost for current month
|
|
127
|
+
*/
|
|
128
|
+
function getMonthlyCost(tracker) {
|
|
129
|
+
const now = new Date();
|
|
130
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
131
|
+
|
|
132
|
+
let total = 0;
|
|
133
|
+
for (const [day, cost] of Object.entries(tracker.byDay)) {
|
|
134
|
+
const dayDate = new Date(day);
|
|
135
|
+
if (dayDate >= monthStart && dayDate <= now) {
|
|
136
|
+
total += cost;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return total;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get costs grouped by model
|
|
145
|
+
* @param {Object} tracker - Cost tracker instance
|
|
146
|
+
* @returns {Object} Costs by model
|
|
147
|
+
*/
|
|
148
|
+
function getCostByModel(tracker) {
|
|
149
|
+
return { ...tracker.byModel };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get costs grouped by provider
|
|
154
|
+
* @param {Object} tracker - Cost tracker instance
|
|
155
|
+
* @returns {Object} Costs by provider
|
|
156
|
+
*/
|
|
157
|
+
function getCostByProvider(tracker) {
|
|
158
|
+
return { ...tracker.byProvider };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Persist costs to disk
|
|
163
|
+
* @param {Object} tracker - Cost tracker instance
|
|
164
|
+
* @param {string} filePath - Path to save file
|
|
165
|
+
*/
|
|
166
|
+
function persistCosts(tracker, filePath) {
|
|
167
|
+
const data = {
|
|
168
|
+
records: tracker.records,
|
|
169
|
+
savedAt: new Date().toISOString(),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
tracker.fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Load costs from disk
|
|
177
|
+
* @param {string} filePath - Path to load from
|
|
178
|
+
* @param {Object} options - Options including fs module
|
|
179
|
+
* @returns {Object} Cost tracker instance with loaded data
|
|
180
|
+
*/
|
|
181
|
+
function loadCosts(filePath, options = {}) {
|
|
182
|
+
const fsModule = options.fs || fs;
|
|
183
|
+
const tracker = createCostTracker(options);
|
|
184
|
+
|
|
185
|
+
if (!fsModule.existsSync(filePath)) {
|
|
186
|
+
return tracker;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const data = JSON.parse(fsModule.readFileSync(filePath, 'utf-8'));
|
|
191
|
+
|
|
192
|
+
if (data.records && Array.isArray(data.records)) {
|
|
193
|
+
for (const record of data.records) {
|
|
194
|
+
recordCost(tracker, record);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
// Return empty tracker on error
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return tracker;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
createCostTracker,
|
|
206
|
+
recordCost,
|
|
207
|
+
getAgentCost,
|
|
208
|
+
getSessionCost,
|
|
209
|
+
getDailyCost,
|
|
210
|
+
getWeeklyCost,
|
|
211
|
+
getMonthlyCost,
|
|
212
|
+
getCostByModel,
|
|
213
|
+
getCostByProvider,
|
|
214
|
+
persistCosts,
|
|
215
|
+
loadCosts,
|
|
216
|
+
};
|