@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,164 @@
1
+ /**
2
+ * API Key Manager - Session-based API key storage and management
3
+ *
4
+ * Purpose:
5
+ * - Store API keys per session (no persistent storage)
6
+ * - Manage both platform (Loxia) and vendor-specific keys
7
+ * - Provide keys to AI services based on model requirements
8
+ */
9
+
10
+ class ApiKeyManager {
11
+ constructor(logger) {
12
+ this.logger = logger;
13
+
14
+ // Session-based storage: sessionId -> { loxiaApiKey, vendorKeys: { anthropic, openai, etc. } }
15
+ this.sessionKeys = new Map();
16
+
17
+ // Global fallback keys (can be set via config, but session keys take priority)
18
+ this.globalKeys = {
19
+ loxiaApiKey: null,
20
+ vendorKeys: {}
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Set API keys for a session
26
+ * @param {string} sessionId - Session identifier
27
+ * @param {Object} keys - API keys object
28
+ * @param {string} keys.loxiaApiKey - Loxia platform API key
29
+ * @param {Object} keys.vendorKeys - Vendor-specific keys { anthropic: 'key', openai: 'key', etc. }
30
+ */
31
+ setSessionKeys(sessionId, keys) {
32
+ if (!sessionId) {
33
+ throw new Error('Session ID is required');
34
+ }
35
+
36
+ this.sessionKeys.set(sessionId, {
37
+ loxiaApiKey: keys.loxiaApiKey || null,
38
+ vendorKeys: keys.vendorKeys || {},
39
+ updatedAt: new Date().toISOString()
40
+ });
41
+
42
+ this.logger?.info('API keys updated for session', {
43
+ sessionId,
44
+ hasLoxiaKey: !!keys.loxiaApiKey,
45
+ vendorKeys: Object.keys(keys.vendorKeys || {}),
46
+ timestamp: new Date().toISOString()
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Get API keys for a session
52
+ * @param {string} sessionId - Session identifier
53
+ * @returns {Object} API keys object
54
+ */
55
+ getSessionKeys(sessionId) {
56
+ if (!sessionId) {
57
+ return this.globalKeys;
58
+ }
59
+
60
+ const sessionKeys = this.sessionKeys.get(sessionId);
61
+ if (sessionKeys) {
62
+ return sessionKeys;
63
+ }
64
+
65
+ // Fallback to global keys
66
+ return this.globalKeys;
67
+ }
68
+
69
+ /**
70
+ * Get the appropriate API key for a model/request
71
+ * @param {string} sessionId - Session identifier
72
+ * @param {Object} options - Request options
73
+ * @param {boolean} options.platformProvided - Whether this is a platform-provided model
74
+ * @param {string} options.vendor - Vendor name (anthropic, openai, etc.)
75
+ * @returns {Object} { loxiaApiKey, vendorApiKey }
76
+ */
77
+ getKeysForRequest(sessionId, options = {}) {
78
+ const keys = this.getSessionKeys(sessionId);
79
+
80
+ const result = {
81
+ loxiaApiKey: keys.loxiaApiKey,
82
+ vendorApiKey: null
83
+ };
84
+
85
+ // For direct access models, also include vendor-specific key
86
+ if (!options.platformProvided && options.vendor && keys.vendorKeys) {
87
+ result.vendorApiKey = keys.vendorKeys[options.vendor];
88
+ }
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Remove API keys for a session
95
+ * @param {string} sessionId - Session identifier
96
+ */
97
+ removeSessionKeys(sessionId) {
98
+ if (this.sessionKeys.delete(sessionId)) {
99
+ this.logger?.info('API keys removed for session', { sessionId });
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+
105
+ /**
106
+ * Set global fallback API keys
107
+ * @param {Object} keys - Global API keys
108
+ */
109
+ setGlobalKeys(keys) {
110
+ this.globalKeys = {
111
+ loxiaApiKey: keys.loxiaApiKey || this.globalKeys.loxiaApiKey,
112
+ vendorKeys: { ...this.globalKeys.vendorKeys, ...(keys.vendorKeys || {}) }
113
+ };
114
+
115
+ this.logger?.info('Global API keys updated', {
116
+ hasLoxiaKey: !!this.globalKeys.loxiaApiKey,
117
+ vendorKeys: Object.keys(this.globalKeys.vendorKeys)
118
+ });
119
+ }
120
+
121
+ /**
122
+ * List all active sessions with API keys
123
+ * @returns {Array} Array of session information
124
+ */
125
+ getActiveSessions() {
126
+ return Array.from(this.sessionKeys.entries()).map(([sessionId, keys]) => ({
127
+ sessionId,
128
+ hasLoxiaKey: !!keys.loxiaApiKey,
129
+ vendorKeys: Object.keys(keys.vendorKeys || {}),
130
+ updatedAt: keys.updatedAt
131
+ }));
132
+ }
133
+
134
+ /**
135
+ * Clean up expired sessions (optional)
136
+ * @param {number} maxAge - Maximum age in milliseconds
137
+ */
138
+ cleanupExpiredSessions(maxAge = 24 * 60 * 60 * 1000) { // 24 hours default
139
+ const now = Date.now();
140
+ const expired = [];
141
+
142
+ for (const [sessionId, keys] of this.sessionKeys.entries()) {
143
+ const age = now - new Date(keys.updatedAt).getTime();
144
+ if (age > maxAge) {
145
+ expired.push(sessionId);
146
+ }
147
+ }
148
+
149
+ expired.forEach(sessionId => {
150
+ this.sessionKeys.delete(sessionId);
151
+ });
152
+
153
+ if (expired.length > 0) {
154
+ this.logger?.info('Cleaned up expired API key sessions', {
155
+ expiredSessions: expired.length,
156
+ remainingSessions: this.sessionKeys.size
157
+ });
158
+ }
159
+
160
+ return expired.length;
161
+ }
162
+ }
163
+
164
+ export default ApiKeyManager;
@@ -0,0 +1,366 @@
1
+ /**
2
+ * BenchmarkService - Manages model performance benchmarks for routing decisions
3
+ *
4
+ * Purpose:
5
+ * - Fetch benchmark data from Azure backend
6
+ * - Provide local fallback data
7
+ * - Refresh benchmarks periodically
8
+ * - Supply benchmark data to model router
9
+ */
10
+
11
+ import { MODEL_ROUTER_CONFIG, HTTP_STATUS } from '../utilities/constants.js';
12
+
13
+ class BenchmarkService {
14
+ constructor(config, logger) {
15
+ this.config = config;
16
+ this.logger = logger;
17
+
18
+ this.benchmarks = null;
19
+ this.lastUpdated = null;
20
+ this.isLoading = false;
21
+
22
+ this.azureBackendUrl = 'https://autopilot-api.azurewebsites.net';
23
+ this.refreshInterval = MODEL_ROUTER_CONFIG.BENCHMARK_REFRESH_INTERVAL;
24
+ }
25
+
26
+ /**
27
+ * Initialize benchmark service and load initial data
28
+ */
29
+ async initialize() {
30
+ try {
31
+ await this.loadBenchmarks();
32
+ this._scheduleRefresh();
33
+ this.logger.info('Benchmark service initialized');
34
+ } catch (error) {
35
+ this.logger.error('Failed to initialize benchmark service', { error: error.message });
36
+ // Use fallback data on initialization failure
37
+ this.benchmarks = this._getFallbackBenchmarks();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Get current benchmark data
43
+ */
44
+ getBenchmarks() {
45
+ if (!this.benchmarks) {
46
+ this.logger.warn('No benchmark data available, using fallback');
47
+ return this._getFallbackBenchmarks();
48
+ }
49
+ return this.benchmarks;
50
+ }
51
+
52
+ /**
53
+ * Load benchmarks from Azure backend with fallback
54
+ */
55
+ async loadBenchmarks() {
56
+ if (this.isLoading) {
57
+ this.logger.debug('Benchmark loading already in progress');
58
+ return;
59
+ }
60
+
61
+ this.isLoading = true;
62
+
63
+ try {
64
+ // Try to load from Azure backend
65
+ const benchmarks = await this._fetchFromAzureBackend();
66
+
67
+ if (benchmarks) {
68
+ this.benchmarks = benchmarks;
69
+ this.lastUpdated = new Date();
70
+ this.logger.info('Benchmarks loaded from Azure backend', {
71
+ modelCount: benchmarks.models?.length || 0
72
+ });
73
+ } else {
74
+ throw new Error('No benchmark data received from backend');
75
+ }
76
+
77
+ } catch (error) {
78
+ this.logger.warn('Failed to fetch benchmarks from Azure backend, using fallback', {
79
+ error: error.message
80
+ });
81
+
82
+ // Use fallback data
83
+ this.benchmarks = this._getFallbackBenchmarks();
84
+ this.lastUpdated = new Date();
85
+
86
+ } finally {
87
+ this.isLoading = false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get benchmark comparison table formatted for PHI-4 router
93
+ */
94
+ getBenchmarkTable() {
95
+ const benchmarks = this.getBenchmarks();
96
+
97
+ return {
98
+ lastUpdated: this.lastUpdated?.toISOString() || 'unknown',
99
+ models: benchmarks.models || [],
100
+ tasks: benchmarks.tasks || {},
101
+ summary: this._generateBenchmarkSummary(benchmarks)
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Check if benchmarks need refresh
107
+ */
108
+ needsRefresh() {
109
+ if (!this.lastUpdated) return true;
110
+
111
+ const timeSinceUpdate = Date.now() - this.lastUpdated.getTime();
112
+ return timeSinceUpdate > this.refreshInterval;
113
+ }
114
+
115
+ /**
116
+ * Fetch benchmarks from Azure backend
117
+ * @private
118
+ */
119
+ async _fetchFromAzureBackend() {
120
+ const url = `${this.azureBackendUrl}/llm/model-benchmarks`;
121
+
122
+ // Get platform API key
123
+ let apiKey = null;
124
+ if (this.config.apiKey) {
125
+ apiKey = this.config.apiKey;
126
+ }
127
+
128
+ const fetchOptions = {
129
+ method: 'GET',
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ ...(apiKey && { 'Authorization': `Bearer ${apiKey}` })
133
+ },
134
+ timeout: 10000 // 10 second timeout
135
+ };
136
+
137
+ this.logger.debug('Fetching benchmarks from Azure backend', { url });
138
+
139
+ const response = await fetch(url, fetchOptions);
140
+
141
+ if (!response.ok) {
142
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
143
+ }
144
+
145
+ const data = await response.json();
146
+ return data;
147
+ }
148
+
149
+ /**
150
+ * Get fallback benchmark data
151
+ * @private
152
+ */
153
+ _getFallbackBenchmarks() {
154
+ return {
155
+ version: '1.0.0',
156
+ lastUpdated: new Date().toISOString(),
157
+ source: 'fallback',
158
+ models: [
159
+ {
160
+ name: 'anthropic-sonnet',
161
+ provider: 'anthropic',
162
+ tasks: {
163
+ coding: { score: 85, speed: 'medium', cost: 'medium' },
164
+ analysis: { score: 90, speed: 'medium', cost: 'medium' },
165
+ 'quick-tasks': { score: 75, speed: 'fast', cost: 'low' },
166
+ creative: { score: 88, speed: 'medium', cost: 'medium' }
167
+ },
168
+ strengths: ['reasoning', 'code-understanding', 'analysis'],
169
+ contextWindow: 200000,
170
+ maxTokens: 4096
171
+ },
172
+ {
173
+ name: 'anthropic-opus',
174
+ provider: 'anthropic',
175
+ tasks: {
176
+ coding: { score: 92, speed: 'slow', cost: 'high' },
177
+ analysis: { score: 95, speed: 'slow', cost: 'high' },
178
+ 'quick-tasks': { score: 80, speed: 'medium', cost: 'high' },
179
+ creative: { score: 95, speed: 'slow', cost: 'high' }
180
+ },
181
+ strengths: ['complex-reasoning', 'creative-writing', 'deep-analysis'],
182
+ contextWindow: 200000,
183
+ maxTokens: 4096
184
+ },
185
+ {
186
+ name: 'anthropic-haiku',
187
+ provider: 'anthropic',
188
+ tasks: {
189
+ coding: { score: 70, speed: 'fast', cost: 'low' },
190
+ analysis: { score: 75, speed: 'fast', cost: 'low' },
191
+ 'quick-tasks': { score: 85, speed: 'very-fast', cost: 'very-low' },
192
+ creative: { score: 70, speed: 'fast', cost: 'low' }
193
+ },
194
+ strengths: ['speed', 'simple-tasks', 'cost-efficiency'],
195
+ contextWindow: 200000,
196
+ maxTokens: 4096
197
+ },
198
+ {
199
+ name: 'gpt-4',
200
+ provider: 'openai',
201
+ tasks: {
202
+ coding: { score: 88, speed: 'medium', cost: 'high' },
203
+ analysis: { score: 85, speed: 'medium', cost: 'high' },
204
+ 'quick-tasks': { score: 80, speed: 'medium', cost: 'high' },
205
+ creative: { score: 90, speed: 'medium', cost: 'high' }
206
+ },
207
+ strengths: ['general-purpose', 'coding', 'problem-solving'],
208
+ contextWindow: 128000,
209
+ maxTokens: 4096
210
+ },
211
+ {
212
+ name: 'gpt-4-mini',
213
+ provider: 'openai',
214
+ tasks: {
215
+ coding: { score: 75, speed: 'fast', cost: 'low' },
216
+ analysis: { score: 70, speed: 'fast', cost: 'low' },
217
+ 'quick-tasks': { score: 80, speed: 'very-fast', cost: 'very-low' },
218
+ creative: { score: 72, speed: 'fast', cost: 'low' }
219
+ },
220
+ strengths: ['speed', 'cost-efficiency', 'simple-coding'],
221
+ contextWindow: 128000,
222
+ maxTokens: 16384
223
+ },
224
+ {
225
+ name: 'azure-ai-grok3',
226
+ provider: 'xai',
227
+ tasks: {
228
+ coding: { score: 82, speed: 'medium', cost: 'medium' },
229
+ analysis: { score: 85, speed: 'medium', cost: 'medium' },
230
+ 'quick-tasks': { score: 78, speed: 'fast', cost: 'medium' },
231
+ creative: { score: 88, speed: 'medium', cost: 'medium' }
232
+ },
233
+ strengths: ['reasoning', 'real-time-data', 'analysis'],
234
+ contextWindow: 128000,
235
+ maxTokens: 4096
236
+ },
237
+ {
238
+ name: 'deepseek-r1',
239
+ provider: 'deepseek',
240
+ tasks: {
241
+ coding: { score: 90, speed: 'medium', cost: 'low' },
242
+ analysis: { score: 80, speed: 'medium', cost: 'low' },
243
+ 'quick-tasks': { score: 75, speed: 'fast', cost: 'very-low' },
244
+ creative: { score: 65, speed: 'medium', cost: 'low' }
245
+ },
246
+ strengths: ['coding', 'reasoning', 'cost-efficiency'],
247
+ contextWindow: 128000,
248
+ maxTokens: 8192
249
+ },
250
+ {
251
+ name: 'phi-4',
252
+ provider: 'microsoft',
253
+ tasks: {
254
+ coding: { score: 72, speed: 'fast', cost: 'very-low' },
255
+ analysis: { score: 78, speed: 'fast', cost: 'very-low' },
256
+ 'quick-tasks': { score: 82, speed: 'very-fast', cost: 'very-low' },
257
+ creative: { score: 68, speed: 'fast', cost: 'very-low' }
258
+ },
259
+ strengths: ['speed', 'efficiency', 'small-tasks'],
260
+ contextWindow: 16384,
261
+ maxTokens: 2048
262
+ }
263
+ ],
264
+ tasks: {
265
+ coding: {
266
+ description: 'Programming, debugging, code review, refactoring',
267
+ topModels: ['deepseek-r1', 'anthropic-opus', 'gpt-4', 'anthropic-sonnet']
268
+ },
269
+ analysis: {
270
+ description: 'Data analysis, research, document review, reasoning',
271
+ topModels: ['anthropic-opus', 'anthropic-sonnet', 'azure-ai-grok3', 'gpt-4']
272
+ },
273
+ 'quick-tasks': {
274
+ description: 'Simple questions, formatting, quick edits, summaries',
275
+ topModels: ['anthropic-haiku', 'phi-4', 'gpt-4-mini', 'azure-ai-grok3']
276
+ },
277
+ creative: {
278
+ description: 'Writing, brainstorming, content creation, storytelling',
279
+ topModels: ['anthropic-opus', 'gpt-4', 'azure-ai-grok3', 'anthropic-sonnet']
280
+ }
281
+ }
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Generate benchmark summary for router
287
+ * @private
288
+ */
289
+ _generateBenchmarkSummary(benchmarks) {
290
+ const models = benchmarks.models || [];
291
+
292
+ return {
293
+ totalModels: models.length,
294
+ taskCategories: Object.keys(benchmarks.tasks || {}),
295
+ bestForCoding: this._getBestModelForTask(models, 'coding'),
296
+ bestForAnalysis: this._getBestModelForTask(models, 'analysis'),
297
+ bestForQuickTasks: this._getBestModelForTask(models, 'quick-tasks'),
298
+ bestForCreative: this._getBestModelForTask(models, 'creative'),
299
+ fastestModel: this._getFastestModel(models),
300
+ mostCostEfficient: this._getMostCostEfficient(models)
301
+ };
302
+ }
303
+
304
+ _getBestModelForTask(models, task) {
305
+ return models
306
+ .filter(m => m.tasks && m.tasks[task])
307
+ .sort((a, b) => (b.tasks[task].score || 0) - (a.tasks[task].score || 0))[0]?.name || 'unknown';
308
+ }
309
+
310
+ _getFastestModel(models) {
311
+ const speedOrder = { 'very-fast': 4, 'fast': 3, 'medium': 2, 'slow': 1 };
312
+ return models
313
+ .sort((a, b) => {
314
+ const aSpeed = Math.max(...Object.values(a.tasks || {}).map(t => speedOrder[t.speed] || 0));
315
+ const bSpeed = Math.max(...Object.values(b.tasks || {}).map(t => speedOrder[t.speed] || 0));
316
+ return bSpeed - aSpeed;
317
+ })[0]?.name || 'unknown';
318
+ }
319
+
320
+ _getMostCostEfficient(models) {
321
+ const costOrder = { 'very-low': 4, 'low': 3, 'medium': 2, 'high': 1 };
322
+ return models
323
+ .sort((a, b) => {
324
+ const aCost = Math.max(...Object.values(a.tasks || {}).map(t => costOrder[t.cost] || 0));
325
+ const bCost = Math.max(...Object.values(b.tasks || {}).map(t => costOrder[t.cost] || 0));
326
+ return bCost - aCost;
327
+ })[0]?.name || 'unknown';
328
+ }
329
+
330
+ /**
331
+ * Schedule periodic refresh
332
+ * @private
333
+ */
334
+ _scheduleRefresh() {
335
+ setInterval(async () => {
336
+ if (this.needsRefresh()) {
337
+ this.logger.debug('Refreshing benchmark data');
338
+ await this.loadBenchmarks();
339
+ }
340
+ }, this.refreshInterval);
341
+ }
342
+
343
+ /**
344
+ * Force refresh benchmarks
345
+ */
346
+ async forceRefresh() {
347
+ this.logger.info('Force refreshing benchmark data');
348
+ await this.loadBenchmarks();
349
+ }
350
+
351
+ /**
352
+ * Get service status
353
+ */
354
+ getStatus() {
355
+ return {
356
+ initialized: !!this.benchmarks,
357
+ lastUpdated: this.lastUpdated?.toISOString() || null,
358
+ source: this.benchmarks?.source || 'unknown',
359
+ modelCount: this.benchmarks?.models?.length || 0,
360
+ isLoading: this.isLoading,
361
+ needsRefresh: this.needsRefresh()
362
+ };
363
+ }
364
+ }
365
+
366
+ export default BenchmarkService;