@loxia-labs/loxia-autopilot-one 1.0.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.
Files changed (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
@@ -0,0 +1,539 @@
1
+ import { BUDGET_LIMITS, COST_PER_TOKEN, USAGE_ALERTS } from '../utilities/constants.js';
2
+
3
+ /**
4
+ * Budget and usage tracking service for monitoring AI model costs and token usage
5
+ */
6
+ export class BudgetService {
7
+ constructor(config, logger) {
8
+ this.config = config || {};
9
+ this.logger = logger;
10
+ this.usage = {
11
+ daily: new Map(),
12
+ weekly: new Map(),
13
+ monthly: new Map(),
14
+ total: {
15
+ tokens: 0,
16
+ cost: 0,
17
+ requests: 0
18
+ }
19
+ };
20
+
21
+ this.budgets = {
22
+ daily: BUDGET_LIMITS.DAILY,
23
+ weekly: BUDGET_LIMITS.WEEKLY,
24
+ monthly: BUDGET_LIMITS.MONTHLY
25
+ };
26
+
27
+ this.alerts = {
28
+ enabled: true,
29
+ thresholds: USAGE_ALERTS.THRESHOLDS,
30
+ lastAlertTimes: new Map()
31
+ };
32
+
33
+ this.loadUsageData();
34
+ this.setupPeriodicSave();
35
+ }
36
+
37
+ /**
38
+ * Track token usage for a specific model and agent
39
+ * @param {string} agentId - Agent identifier
40
+ * @param {string} modelId - Model identifier
41
+ * @param {Object} tokenUsage - Token usage data
42
+ * @param {number} tokenUsage.prompt_tokens - Input tokens used
43
+ * @param {number} tokenUsage.completion_tokens - Output tokens used
44
+ * @param {number} tokenUsage.total_tokens - Total tokens used
45
+ * @returns {Object} Updated usage statistics and cost
46
+ */
47
+ trackUsage(agentId, modelId, tokenUsage) {
48
+ try {
49
+ const cost = this.calculateCost(modelId, tokenUsage);
50
+ const now = new Date();
51
+ const dayKey = this.getDayKey(now);
52
+ const weekKey = this.getWeekKey(now);
53
+ const monthKey = this.getMonthKey(now);
54
+
55
+ // Initialize usage entries if they don't exist
56
+ this.initializeUsageEntry(this.usage.daily, dayKey);
57
+ this.initializeUsageEntry(this.usage.weekly, weekKey);
58
+ this.initializeUsageEntry(this.usage.monthly, monthKey);
59
+
60
+ // Update usage statistics
61
+ const usageData = {
62
+ agentId,
63
+ modelId,
64
+ tokens: tokenUsage.total_tokens,
65
+ cost,
66
+ timestamp: now.toISOString()
67
+ };
68
+
69
+ this.updateUsageEntry(this.usage.daily.get(dayKey), usageData);
70
+ this.updateUsageEntry(this.usage.weekly.get(weekKey), usageData);
71
+ this.updateUsageEntry(this.usage.monthly.get(monthKey), usageData);
72
+
73
+ // Update total usage
74
+ this.usage.total.tokens += tokenUsage.total_tokens;
75
+ this.usage.total.cost += cost;
76
+ this.usage.total.requests += 1;
77
+
78
+ // Check budget limits and send alerts if necessary
79
+ this.checkBudgetLimits(dayKey, weekKey, monthKey);
80
+
81
+ this.logger.info('Usage tracked', {
82
+ agentId,
83
+ modelId,
84
+ tokens: tokenUsage.total_tokens,
85
+ cost,
86
+ totalCost: this.usage.total.cost
87
+ });
88
+
89
+ return {
90
+ cost,
91
+ totalCost: this.usage.total.cost,
92
+ totalTokens: this.usage.total.tokens,
93
+ dailyUsage: this.usage.daily.get(dayKey),
94
+ budgetRemaining: this.getRemainingBudget()
95
+ };
96
+
97
+ } catch (error) {
98
+ this.logger.error('Failed to track usage', { error: error.message, agentId, modelId });
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Calculate cost based on model and token usage
105
+ * @param {string} modelId - Model identifier
106
+ * @param {Object} tokenUsage - Token usage data
107
+ * @returns {number} Cost in USD
108
+ */
109
+ calculateCost(modelId, tokenUsage) {
110
+ const modelCosts = COST_PER_TOKEN[modelId];
111
+
112
+ if (!modelCosts) {
113
+ this.logger.warn('Unknown model for cost calculation', { modelId });
114
+ return 0;
115
+ }
116
+
117
+ const inputCost = (tokenUsage.prompt_tokens || 0) * modelCosts.input;
118
+ const outputCost = (tokenUsage.completion_tokens || 0) * modelCosts.output;
119
+
120
+ return inputCost + outputCost;
121
+ }
122
+
123
+ /**
124
+ * Get current usage statistics
125
+ * @param {string} period - 'daily', 'weekly', 'monthly', or 'total'
126
+ * @param {Date} date - Date for the period (optional, defaults to now)
127
+ * @returns {Object} Usage statistics
128
+ */
129
+ getUsage(period = 'daily', date = new Date()) {
130
+ switch (period) {
131
+ case 'daily':
132
+ return this.usage.daily.get(this.getDayKey(date)) || this.createEmptyUsage();
133
+ case 'weekly':
134
+ return this.usage.weekly.get(this.getWeekKey(date)) || this.createEmptyUsage();
135
+ case 'monthly':
136
+ return this.usage.monthly.get(this.getMonthKey(date)) || this.createEmptyUsage();
137
+ case 'total':
138
+ return { ...this.usage.total };
139
+ default:
140
+ throw new Error(`Invalid period: ${period}`);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Get usage by agent
146
+ * @param {string} agentId - Agent identifier
147
+ * @param {string} period - 'daily', 'weekly', 'monthly', or 'total'
148
+ * @returns {Object} Agent-specific usage statistics
149
+ */
150
+ getAgentUsage(agentId, period = 'daily') {
151
+ const usage = this.getUsage(period);
152
+ return usage.byAgent?.[agentId] || this.createEmptyUsage();
153
+ }
154
+
155
+ /**
156
+ * Get usage by model
157
+ * @param {string} modelId - Model identifier
158
+ * @param {string} period - 'daily', 'weekly', 'monthly', or 'total'
159
+ * @returns {Object} Model-specific usage statistics
160
+ */
161
+ getModelUsage(modelId, period = 'daily') {
162
+ const usage = this.getUsage(period);
163
+ return usage.byModel?.[modelId] || this.createEmptyUsage();
164
+ }
165
+
166
+ /**
167
+ * Set budget limits
168
+ * @param {Object} budgets - Budget configuration
169
+ * @param {number} budgets.daily - Daily budget limit in USD
170
+ * @param {number} budgets.weekly - Weekly budget limit in USD
171
+ * @param {number} budgets.monthly - Monthly budget limit in USD
172
+ */
173
+ setBudgets(budgets) {
174
+ if (budgets.daily !== undefined) this.budgets.daily = budgets.daily;
175
+ if (budgets.weekly !== undefined) this.budgets.weekly = budgets.weekly;
176
+ if (budgets.monthly !== undefined) this.budgets.monthly = budgets.monthly;
177
+
178
+ this.logger.info('Budget limits updated', this.budgets);
179
+ this.saveUsageData();
180
+ }
181
+
182
+ /**
183
+ * Get remaining budget for each period
184
+ * @returns {Object} Remaining budget amounts
185
+ */
186
+ getRemainingBudget() {
187
+ const now = new Date();
188
+ const dailyUsage = this.getUsage('daily', now);
189
+ const weeklyUsage = this.getUsage('weekly', now);
190
+ const monthlyUsage = this.getUsage('monthly', now);
191
+
192
+ return {
193
+ daily: Math.max(0, this.budgets.daily - dailyUsage.cost),
194
+ weekly: Math.max(0, this.budgets.weekly - weeklyUsage.cost),
195
+ monthly: Math.max(0, this.budgets.monthly - monthlyUsage.cost)
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Check if usage is within budget limits
201
+ * @param {string} period - 'daily', 'weekly', or 'monthly'
202
+ * @returns {boolean} True if within budget
203
+ */
204
+ isWithinBudget(period = 'daily') {
205
+ const usage = this.getUsage(period);
206
+ const budget = this.budgets[period];
207
+ return usage.cost <= budget;
208
+ }
209
+
210
+ /**
211
+ * Get usage trends and analytics
212
+ * @param {number} days - Number of days to analyze
213
+ * @returns {Object} Usage trends and analytics
214
+ */
215
+ getUsageTrends(days = 30) {
216
+ const trends = {
217
+ dailyAverages: {
218
+ cost: 0,
219
+ tokens: 0,
220
+ requests: 0
221
+ },
222
+ topAgents: [],
223
+ topModels: [],
224
+ costByDay: [],
225
+ tokensByDay: []
226
+ };
227
+
228
+ const now = new Date();
229
+ let totalCost = 0;
230
+ let totalTokens = 0;
231
+ let totalRequests = 0;
232
+ const agentUsage = new Map();
233
+ const modelUsage = new Map();
234
+
235
+ // Analyze daily usage for the specified period
236
+ for (let i = 0; i < days; i++) {
237
+ const date = new Date(now);
238
+ date.setDate(date.getDate() - i);
239
+ const dayKey = this.getDayKey(date);
240
+ const dayUsage = this.usage.daily.get(dayKey);
241
+
242
+ if (dayUsage) {
243
+ totalCost += dayUsage.cost;
244
+ totalTokens += dayUsage.tokens;
245
+ totalRequests += dayUsage.requests;
246
+
247
+ trends.costByDay.unshift({ date: dayKey, cost: dayUsage.cost });
248
+ trends.tokensByDay.unshift({ date: dayKey, tokens: dayUsage.tokens });
249
+
250
+ // Aggregate agent usage
251
+ Object.entries(dayUsage.byAgent || {}).forEach(([agentId, usage]) => {
252
+ if (!agentUsage.has(agentId)) {
253
+ agentUsage.set(agentId, { cost: 0, tokens: 0, requests: 0 });
254
+ }
255
+ const current = agentUsage.get(agentId);
256
+ current.cost += usage.cost;
257
+ current.tokens += usage.tokens;
258
+ current.requests += usage.requests;
259
+ });
260
+
261
+ // Aggregate model usage
262
+ Object.entries(dayUsage.byModel || {}).forEach(([modelId, usage]) => {
263
+ if (!modelUsage.has(modelId)) {
264
+ modelUsage.set(modelId, { cost: 0, tokens: 0, requests: 0 });
265
+ }
266
+ const current = modelUsage.get(modelId);
267
+ current.cost += usage.cost;
268
+ current.tokens += usage.tokens;
269
+ current.requests += usage.requests;
270
+ });
271
+ } else {
272
+ trends.costByDay.unshift({ date: dayKey, cost: 0 });
273
+ trends.tokensByDay.unshift({ date: dayKey, tokens: 0 });
274
+ }
275
+ }
276
+
277
+ // Calculate averages
278
+ trends.dailyAverages.cost = totalCost / days;
279
+ trends.dailyAverages.tokens = totalTokens / days;
280
+ trends.dailyAverages.requests = totalRequests / days;
281
+
282
+ // Sort and get top agents and models
283
+ trends.topAgents = Array.from(agentUsage.entries())
284
+ .map(([id, usage]) => ({ id, ...usage }))
285
+ .sort((a, b) => b.cost - a.cost)
286
+ .slice(0, 10);
287
+
288
+ trends.topModels = Array.from(modelUsage.entries())
289
+ .map(([id, usage]) => ({ id, ...usage }))
290
+ .sort((a, b) => b.cost - a.cost)
291
+ .slice(0, 10);
292
+
293
+ return trends;
294
+ }
295
+
296
+ /**
297
+ * Export usage data for reporting
298
+ * @param {string} format - 'json' or 'csv'
299
+ * @param {Date} startDate - Start date for export
300
+ * @param {Date} endDate - End date for export
301
+ * @returns {string} Exported data
302
+ */
303
+ exportUsageData(format = 'json', startDate = null, endDate = null) {
304
+ const data = {
305
+ budgets: this.budgets,
306
+ total: this.usage.total,
307
+ daily: {},
308
+ weekly: {},
309
+ monthly: {}
310
+ };
311
+
312
+ // Filter data by date range if specified
313
+ const filterByDateRange = (entries, keyFormatter) => {
314
+ const filtered = {};
315
+ entries.forEach((usage, key) => {
316
+ const date = keyFormatter(key);
317
+ if ((!startDate || date >= startDate) && (!endDate || date <= endDate)) {
318
+ filtered[key] = usage;
319
+ }
320
+ });
321
+ return filtered;
322
+ };
323
+
324
+ if (startDate || endDate) {
325
+ data.daily = filterByDateRange(this.usage.daily, key => new Date(key));
326
+ data.weekly = filterByDateRange(this.usage.weekly, key => new Date(key));
327
+ data.monthly = filterByDateRange(this.usage.monthly, key => new Date(key + '-01'));
328
+ } else {
329
+ this.usage.daily.forEach((usage, key) => data.daily[key] = usage);
330
+ this.usage.weekly.forEach((usage, key) => data.weekly[key] = usage);
331
+ this.usage.monthly.forEach((usage, key) => data.monthly[key] = usage);
332
+ }
333
+
334
+ if (format === 'csv') {
335
+ return this.convertToCSV(data);
336
+ }
337
+
338
+ return JSON.stringify(data, null, 2);
339
+ }
340
+
341
+ // Private helper methods
342
+
343
+ initializeUsageEntry(usageMap, key) {
344
+ if (!usageMap.has(key)) {
345
+ usageMap.set(key, this.createEmptyUsage());
346
+ }
347
+ }
348
+
349
+ createEmptyUsage() {
350
+ return {
351
+ cost: 0,
352
+ tokens: 0,
353
+ requests: 0,
354
+ byAgent: {},
355
+ byModel: {}
356
+ };
357
+ }
358
+
359
+ updateUsageEntry(entry, usageData) {
360
+ entry.cost += usageData.cost;
361
+ entry.tokens += usageData.tokens;
362
+ entry.requests += 1;
363
+
364
+ // Update by agent
365
+ if (!entry.byAgent[usageData.agentId]) {
366
+ entry.byAgent[usageData.agentId] = this.createEmptyUsage();
367
+ }
368
+ entry.byAgent[usageData.agentId].cost += usageData.cost;
369
+ entry.byAgent[usageData.agentId].tokens += usageData.tokens;
370
+ entry.byAgent[usageData.agentId].requests += 1;
371
+
372
+ // Update by model
373
+ if (!entry.byModel[usageData.modelId]) {
374
+ entry.byModel[usageData.modelId] = this.createEmptyUsage();
375
+ }
376
+ entry.byModel[usageData.modelId].cost += usageData.cost;
377
+ entry.byModel[usageData.modelId].tokens += usageData.tokens;
378
+ entry.byModel[usageData.modelId].requests += 1;
379
+ }
380
+
381
+ checkBudgetLimits(dayKey, weekKey, monthKey) {
382
+ const dailyUsage = this.usage.daily.get(dayKey);
383
+ const weeklyUsage = this.usage.weekly.get(weekKey);
384
+ const monthlyUsage = this.usage.monthly.get(monthKey);
385
+
386
+ // Check daily budget
387
+ if (dailyUsage.cost > this.budgets.daily) {
388
+ this.sendBudgetAlert('daily', dailyUsage.cost, this.budgets.daily);
389
+ }
390
+
391
+ // Check weekly budget
392
+ if (weeklyUsage.cost > this.budgets.weekly) {
393
+ this.sendBudgetAlert('weekly', weeklyUsage.cost, this.budgets.weekly);
394
+ }
395
+
396
+ // Check monthly budget
397
+ if (monthlyUsage.cost > this.budgets.monthly) {
398
+ this.sendBudgetAlert('monthly', monthlyUsage.cost, this.budgets.monthly);
399
+ }
400
+
401
+ // Check threshold alerts
402
+ this.checkThresholdAlerts(dailyUsage, weeklyUsage, monthlyUsage);
403
+ }
404
+
405
+ checkThresholdAlerts(dailyUsage, weeklyUsage, monthlyUsage) {
406
+ const thresholds = this.alerts.thresholds;
407
+
408
+ this.checkThreshold('daily', dailyUsage.cost, this.budgets.daily, thresholds);
409
+ this.checkThreshold('weekly', weeklyUsage.cost, this.budgets.weekly, thresholds);
410
+ this.checkThreshold('monthly', monthlyUsage.cost, this.budgets.monthly, thresholds);
411
+ }
412
+
413
+ checkThreshold(period, usage, budget, thresholds) {
414
+ const percentage = (usage / budget) * 100;
415
+
416
+ for (const threshold of thresholds) {
417
+ if (percentage >= threshold) {
418
+ const alertKey = `${period}_${threshold}`;
419
+ const lastAlert = this.alerts.lastAlertTimes.get(alertKey);
420
+ const now = Date.now();
421
+
422
+ // Only send alert if we haven't sent one in the last hour
423
+ if (!lastAlert || (now - lastAlert) > 3600000) {
424
+ this.sendThresholdAlert(period, percentage, threshold, usage, budget);
425
+ this.alerts.lastAlertTimes.set(alertKey, now);
426
+ }
427
+ break; // Only send the highest threshold alert
428
+ }
429
+ }
430
+ }
431
+
432
+ sendBudgetAlert(period, currentUsage, budgetLimit) {
433
+ this.logger.warn('Budget limit exceeded', {
434
+ period,
435
+ currentUsage,
436
+ budgetLimit,
437
+ excess: currentUsage - budgetLimit
438
+ });
439
+
440
+ // Emit event for external handling
441
+ if (typeof process !== 'undefined' && process.emit) {
442
+ process.emit('budgetAlert', {
443
+ type: 'budget_exceeded',
444
+ period,
445
+ currentUsage,
446
+ budgetLimit,
447
+ excess: currentUsage - budgetLimit
448
+ });
449
+ }
450
+ }
451
+
452
+ sendThresholdAlert(period, percentage, threshold, usage, budget) {
453
+ this.logger.warn('Budget threshold reached', {
454
+ period,
455
+ percentage: Math.round(percentage),
456
+ threshold,
457
+ usage,
458
+ budget
459
+ });
460
+
461
+ // Emit event for external handling
462
+ if (typeof process !== 'undefined' && process.emit) {
463
+ process.emit('budgetAlert', {
464
+ type: 'threshold_reached',
465
+ period,
466
+ percentage: Math.round(percentage),
467
+ threshold,
468
+ usage,
469
+ budget
470
+ });
471
+ }
472
+ }
473
+
474
+ getDayKey(date) {
475
+ return date.toISOString().split('T')[0];
476
+ }
477
+
478
+ getWeekKey(date) {
479
+ const year = date.getFullYear();
480
+ const weekNum = this.getWeekNumber(date);
481
+ return `${year}-W${weekNum.toString().padStart(2, '0')}`;
482
+ }
483
+
484
+ getMonthKey(date) {
485
+ const year = date.getFullYear();
486
+ const month = date.getMonth() + 1;
487
+ return `${year}-${month.toString().padStart(2, '0')}`;
488
+ }
489
+
490
+ getWeekNumber(date) {
491
+ const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
492
+ const pastDaysOfYear = (date - firstDayOfYear) / 86400000;
493
+ return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
494
+ }
495
+
496
+ convertToCSV(data) {
497
+ const rows = [];
498
+ rows.push(['Period', 'Date', 'Cost', 'Tokens', 'Requests', 'Agent', 'Model']);
499
+
500
+ // Convert daily data
501
+ Object.entries(data.daily).forEach(([date, usage]) => {
502
+ rows.push(['Daily', date, usage.cost, usage.tokens, usage.requests, '', '']);
503
+
504
+ Object.entries(usage.byAgent || {}).forEach(([agentId, agentUsage]) => {
505
+ rows.push(['Daily', date, agentUsage.cost, agentUsage.tokens, agentUsage.requests, agentId, '']);
506
+ });
507
+ });
508
+
509
+ return rows.map(row => row.join(',')).join('\n');
510
+ }
511
+
512
+ loadUsageData() {
513
+ try {
514
+ // In a real implementation, this would load from persistent storage
515
+ // For now, we'll just initialize with empty data
516
+ this.logger.info('Budget service initialized');
517
+ } catch (error) {
518
+ this.logger.error('Failed to load usage data', { error: error.message });
519
+ }
520
+ }
521
+
522
+ saveUsageData() {
523
+ try {
524
+ // In a real implementation, this would save to persistent storage
525
+ this.logger.debug('Usage data saved');
526
+ } catch (error) {
527
+ this.logger.error('Failed to save usage data', { error: error.message });
528
+ }
529
+ }
530
+
531
+ setupPeriodicSave() {
532
+ // Save usage data every 5 minutes
533
+ setInterval(() => {
534
+ this.saveUsageData();
535
+ }, 300000);
536
+ }
537
+ }
538
+
539
+ export default BudgetService;