@loxia-labs/loxia-autopilot-one 1.0.1 → 1.0.4

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 (120) hide show
  1. package/README.md +44 -54
  2. package/bin/cli.js +1 -115
  3. package/bin/loxia-terminal-v2.js +3 -0
  4. package/bin/loxia-terminal.js +3 -0
  5. package/bin/start-with-terminal.js +3 -0
  6. package/package.json +15 -15
  7. package/scripts/install-scanners.js +1 -235
  8. package/src/analyzers/CSSAnalyzer.js +1 -297
  9. package/src/analyzers/ConfigValidator.js +1 -690
  10. package/src/analyzers/ESLintAnalyzer.js +1 -320
  11. package/src/analyzers/JavaScriptAnalyzer.js +1 -261
  12. package/src/analyzers/PrettierFormatter.js +1 -247
  13. package/src/analyzers/PythonAnalyzer.js +1 -266
  14. package/src/analyzers/SecurityAnalyzer.js +1 -729
  15. package/src/analyzers/TypeScriptAnalyzer.js +1 -247
  16. package/src/analyzers/codeCloneDetector/analyzer.js +1 -344
  17. package/src/analyzers/codeCloneDetector/detector.js +1 -203
  18. package/src/analyzers/codeCloneDetector/index.js +1 -160
  19. package/src/analyzers/codeCloneDetector/parser.js +1 -199
  20. package/src/analyzers/codeCloneDetector/reporter.js +1 -148
  21. package/src/analyzers/codeCloneDetector/scanner.js +1 -59
  22. package/src/core/agentPool.js +1 -1474
  23. package/src/core/agentScheduler.js +1 -2147
  24. package/src/core/contextManager.js +1 -709
  25. package/src/core/messageProcessor.js +1 -732
  26. package/src/core/orchestrator.js +1 -548
  27. package/src/core/stateManager.js +1 -877
  28. package/src/index.js +1 -631
  29. package/src/interfaces/cli.js +1 -549
  30. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +1 -0
  31. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +1 -0
  32. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +1 -0
  33. package/src/interfaces/terminal/__tests__/smoke/components.test.js +1 -0
  34. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +1 -0
  35. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +1 -0
  36. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +1 -0
  37. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +1 -0
  38. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +1 -0
  39. package/src/interfaces/terminal/api/apiClient.js +1 -0
  40. package/src/interfaces/terminal/api/messageRouter.js +1 -0
  41. package/src/interfaces/terminal/api/session.js +1 -0
  42. package/src/interfaces/terminal/api/websocket.js +1 -0
  43. package/src/interfaces/terminal/components/AgentCreator.js +1 -0
  44. package/src/interfaces/terminal/components/AgentEditor.js +1 -0
  45. package/src/interfaces/terminal/components/AgentSwitcher.js +1 -0
  46. package/src/interfaces/terminal/components/ErrorBoundary.js +1 -0
  47. package/src/interfaces/terminal/components/ErrorPanel.js +1 -0
  48. package/src/interfaces/terminal/components/Header.js +1 -0
  49. package/src/interfaces/terminal/components/HelpPanel.js +1 -0
  50. package/src/interfaces/terminal/components/InputBox.js +1 -0
  51. package/src/interfaces/terminal/components/Layout.js +1 -0
  52. package/src/interfaces/terminal/components/LoadingSpinner.js +1 -0
  53. package/src/interfaces/terminal/components/MessageList.js +1 -0
  54. package/src/interfaces/terminal/components/MultilineTextInput.js +1 -0
  55. package/src/interfaces/terminal/components/SearchPanel.js +1 -0
  56. package/src/interfaces/terminal/components/SettingsPanel.js +1 -0
  57. package/src/interfaces/terminal/components/StatusBar.js +1 -0
  58. package/src/interfaces/terminal/components/TextInput.js +1 -0
  59. package/src/interfaces/terminal/config/agentEditorConstants.js +1 -0
  60. package/src/interfaces/terminal/config/constants.js +1 -0
  61. package/src/interfaces/terminal/index.js +1 -0
  62. package/src/interfaces/terminal/state/useAgentControl.js +1 -0
  63. package/src/interfaces/terminal/state/useAgents.js +1 -0
  64. package/src/interfaces/terminal/state/useConnection.js +1 -0
  65. package/src/interfaces/terminal/state/useMessages.js +1 -0
  66. package/src/interfaces/terminal/state/useTools.js +1 -0
  67. package/src/interfaces/terminal/utils/debugLogger.js +1 -0
  68. package/src/interfaces/terminal/utils/settingsStorage.js +1 -0
  69. package/src/interfaces/terminal/utils/theme.js +1 -0
  70. package/src/interfaces/webServer.js +1 -2162
  71. package/src/modules/fileExplorer/controller.js +1 -280
  72. package/src/modules/fileExplorer/index.js +1 -37
  73. package/src/modules/fileExplorer/middleware.js +1 -92
  74. package/src/modules/fileExplorer/routes.js +1 -125
  75. package/src/modules/fileExplorer/types.js +1 -44
  76. package/src/services/aiService.js +1 -1232
  77. package/src/services/apiKeyManager.js +1 -164
  78. package/src/services/benchmarkService.js +1 -366
  79. package/src/services/budgetService.js +1 -539
  80. package/src/services/contextInjectionService.js +1 -247
  81. package/src/services/conversationCompactionService.js +1 -637
  82. package/src/services/errorHandler.js +1 -810
  83. package/src/services/fileAttachmentService.js +1 -544
  84. package/src/services/modelRouterService.js +1 -366
  85. package/src/services/modelsService.js +1 -322
  86. package/src/services/qualityInspector.js +1 -796
  87. package/src/services/tokenCountingService.js +1 -536
  88. package/src/tools/agentCommunicationTool.js +1 -1344
  89. package/src/tools/agentDelayTool.js +1 -485
  90. package/src/tools/asyncToolManager.js +1 -604
  91. package/src/tools/baseTool.js +1 -800
  92. package/src/tools/browserTool.js +1 -920
  93. package/src/tools/cloneDetectionTool.js +1 -621
  94. package/src/tools/dependencyResolverTool.js +1 -1215
  95. package/src/tools/fileContentReplaceTool.js +1 -875
  96. package/src/tools/fileSystemTool.js +1 -1107
  97. package/src/tools/fileTreeTool.js +1 -853
  98. package/src/tools/imageTool.js +1 -901
  99. package/src/tools/importAnalyzerTool.js +1 -1060
  100. package/src/tools/jobDoneTool.js +1 -248
  101. package/src/tools/seekTool.js +1 -956
  102. package/src/tools/staticAnalysisTool.js +1 -1778
  103. package/src/tools/taskManagerTool.js +1 -2873
  104. package/src/tools/terminalTool.js +1 -2304
  105. package/src/tools/webTool.js +1 -1430
  106. package/src/types/agent.js +1 -519
  107. package/src/types/contextReference.js +1 -972
  108. package/src/types/conversation.js +1 -730
  109. package/src/types/toolCommand.js +1 -747
  110. package/src/utilities/attachmentValidator.js +1 -292
  111. package/src/utilities/configManager.js +1 -582
  112. package/src/utilities/constants.js +1 -722
  113. package/src/utilities/directoryAccessManager.js +1 -535
  114. package/src/utilities/fileProcessor.js +1 -307
  115. package/src/utilities/logger.js +1 -436
  116. package/src/utilities/tagParser.js +1 -1246
  117. package/src/utilities/toolConstants.js +1 -317
  118. package/web-ui/build/index.html +2 -2
  119. package/web-ui/build/static/{index-Dy2bYbOa.css → index-CClD1090.css} +1 -1
  120. package/web-ui/build/static/{index-CjkkcnFA.js → index-lCBai6dX.js} +66 -67
@@ -1,536 +1 @@
1
- /**
2
- * TokenCountingService - Accurate token counting for conversation compactization
3
- *
4
- * Purpose:
5
- * - Provide accurate token counting using tiktoken (OpenAI's tokenizer)
6
- * - Support multiple models with different tokenization schemes
7
- * - Determine when conversation compactization should be triggered
8
- * - Cache token counts for performance optimization
9
- * - Provide fallback estimation when accurate counting unavailable
10
- *
11
- * Key Features:
12
- * - Accurate token counting via tiktoken library
13
- * - Model-specific tokenization
14
- * - Intelligent caching with TTL
15
- * - Fast estimation fallback
16
- * - Context window management
17
- * - Compaction trigger detection
18
- */
19
-
20
- import { encoding_for_model } from 'tiktoken';
21
- import {
22
- MODELS,
23
- COMPACTION_CONFIG,
24
- TOKEN_COUNTING_MODES,
25
- } from '../utilities/constants.js';
26
-
27
- class TokenCountingService {
28
- constructor(logger) {
29
- this.logger = logger;
30
-
31
- // Token count cache for performance
32
- this.tokenCache = new Map();
33
-
34
- // Tiktoken encoders cache (reuse encoders for efficiency)
35
- this.encoders = new Map();
36
-
37
- // Supported tiktoken models mapping
38
- this.tiktokenModelMap = {
39
- // Anthropic models use OpenAI's cl100k_base encoding
40
- [MODELS.ANTHROPIC_SONNET]: 'gpt-4',
41
- [MODELS.ANTHROPIC_OPUS]: 'gpt-4',
42
- [MODELS.ANTHROPIC_HAIKU]: 'gpt-4',
43
-
44
- // OpenAI models
45
- [MODELS.GPT_4]: 'gpt-4',
46
- [MODELS.GPT_4_MINI]: 'gpt-4',
47
- 'gpt-4o': 'gpt-4',
48
- 'gpt-4o-mini': 'gpt-4',
49
- 'gpt-4-turbo': 'gpt-4',
50
- 'gpt-3.5-turbo': 'gpt-4', // Uses cl100k_base encoding
51
-
52
- // DeepSeek uses similar tokenization to GPT-4
53
- [MODELS.DEEPSEEK_R1]: 'gpt-4',
54
-
55
- // Phi models - fallback to GPT-4 encoding
56
- [MODELS.PHI_4]: 'gpt-4',
57
-
58
- // Azure AI Foundry models
59
- 'azure-ai-grok3': 'gpt-4',
60
- 'azure-ai-deepseek-r1': 'gpt-4',
61
- 'azure-openai-gpt-5': 'gpt-4',
62
- 'azure-openai-gpt-4': 'gpt-4',
63
- 'azure-openai-gpt-4o': 'gpt-4',
64
-
65
- // Compaction models
66
- [COMPACTION_CONFIG.COMPACTION_MODEL]: 'gpt-4',
67
- [COMPACTION_CONFIG.COMPACTION_MODEL_FALLBACK]: 'gpt-4',
68
- };
69
-
70
- this.logger?.info('TokenCountingService initialized', {
71
- supportedModels: Object.keys(this.tiktokenModelMap).length,
72
- cacheEnabled: true
73
- });
74
- }
75
-
76
- /**
77
- * Count tokens in text using accurate tiktoken encoder
78
- * @param {string} text - Text to count tokens in
79
- * @param {string} model - Model name for appropriate tokenization
80
- * @param {string} mode - Counting mode (accurate, estimated, cached)
81
- * @returns {Promise<number>} Token count
82
- */
83
- async countTokens(text, model, mode = TOKEN_COUNTING_MODES.ACCURATE) {
84
- // Validate input
85
- if (!text || typeof text !== 'string') {
86
- return 0;
87
- }
88
-
89
- // Check cache first if enabled
90
- if (mode === TOKEN_COUNTING_MODES.CACHED) {
91
- const cached = this._getCachedTokenCount(text, model);
92
- if (cached !== null) {
93
- return cached;
94
- }
95
- // Fall through to accurate counting if not cached
96
- }
97
-
98
- // Use fast estimation if requested
99
- if (mode === TOKEN_COUNTING_MODES.ESTIMATED) {
100
- return this._estimateTokens(text);
101
- }
102
-
103
- // Accurate counting with tiktoken
104
- try {
105
- const encoder = await this._getEncoder(model);
106
- const tokens = encoder.encode(text);
107
- const count = tokens.length;
108
-
109
- // Cache the result
110
- this._cacheTokenCount(text, model, count);
111
-
112
- return count;
113
-
114
- } catch (error) {
115
- this.logger?.warn('Tiktoken encoding failed, falling back to estimation', {
116
- model,
117
- error: error.message
118
- });
119
-
120
- // Fallback to estimation
121
- return this._estimateTokens(text);
122
- }
123
- }
124
-
125
- /**
126
- * Estimate conversation token count including all messages
127
- * @param {Array} messages - Array of message objects with { role, content }
128
- * @param {string} model - Model name for tokenization
129
- * @param {string} mode - Counting mode
130
- * @returns {Promise<number>} Total token count for conversation
131
- */
132
- async estimateConversationTokens(messages, model, mode = TOKEN_COUNTING_MODES.ACCURATE) {
133
- if (!Array.isArray(messages) || messages.length === 0) {
134
- return 0;
135
- }
136
-
137
- let totalTokens = 0;
138
-
139
- // Count tokens for each message
140
- for (const message of messages) {
141
- if (!message.content) {
142
- continue;
143
- }
144
-
145
- // Message formatting overhead (role + formatting)
146
- const formattingOverhead = this._getMessageFormattingOverhead(model);
147
-
148
- // Content tokens
149
- const contentTokens = await this.countTokens(message.content, model, mode);
150
-
151
- totalTokens += contentTokens + formattingOverhead;
152
- }
153
-
154
- // Add conversation-level overhead (system instructions, etc.)
155
- const conversationOverhead = this._getConversationOverhead(messages.length);
156
- totalTokens += conversationOverhead;
157
-
158
- this.logger?.debug('Conversation token count', {
159
- model,
160
- messageCount: messages.length,
161
- totalTokens,
162
- mode
163
- });
164
-
165
- return totalTokens;
166
- }
167
-
168
- /**
169
- * Get context window size for a model
170
- * @param {string} model - Model name
171
- * @returns {number} Context window size in tokens
172
- */
173
- getModelContextWindow(model) {
174
- // Model context windows (from aiService model specs and vendor documentation)
175
- const contextWindows = {
176
- // Anthropic Claude models
177
- [MODELS.ANTHROPIC_SONNET]: 200000,
178
- [MODELS.ANTHROPIC_OPUS]: 200000,
179
- [MODELS.ANTHROPIC_HAIKU]: 200000,
180
-
181
- // OpenAI models
182
- [MODELS.GPT_4]: 128000,
183
- [MODELS.GPT_4_MINI]: 128000,
184
- 'gpt-4o': 128000,
185
- 'gpt-4o-mini': 128000,
186
- 'gpt-4-turbo': 128000,
187
- 'gpt-3.5-turbo': 16384,
188
-
189
- // DeepSeek models
190
- [MODELS.DEEPSEEK_R1]: 128000,
191
-
192
- // Phi models
193
- [MODELS.PHI_4]: 16384,
194
-
195
- // Azure AI Foundry models
196
- 'azure-ai-grok3': 128000,
197
- 'azure-ai-deepseek-r1': 128000,
198
- 'azure-openai-gpt-5': 128000,
199
- 'azure-openai-gpt-4': 128000,
200
- 'azure-openai-gpt-4o': 128000,
201
-
202
- // Compaction models
203
- [COMPACTION_CONFIG.COMPACTION_MODEL]: 128000,
204
- [COMPACTION_CONFIG.COMPACTION_MODEL_FALLBACK]: 128000,
205
-
206
- // Router model
207
- 'autopilot-model-router': 16384,
208
- };
209
-
210
- const contextWindow = contextWindows[model];
211
-
212
- if (!contextWindow) {
213
- this.logger?.warn('Unknown model context window, using default', {
214
- model,
215
- defaultWindow: 128000
216
- });
217
- return 128000; // Default to 128k
218
- }
219
-
220
- return contextWindow;
221
- }
222
-
223
- /**
224
- * Get maximum output tokens for a model
225
- * @param {string} model - Model name
226
- * @returns {number} Maximum output tokens
227
- */
228
- getModelMaxOutputTokens(model) {
229
- // Max output tokens - increased to 8K-20K where supported for better responses
230
- // Note: These are conservative estimates to ensure compaction triggers appropriately
231
- const maxOutputTokens = {
232
- // Anthropic Claude models - support up to 8K output
233
- [MODELS.ANTHROPIC_SONNET]: 8192,
234
- [MODELS.ANTHROPIC_OPUS]: 8192,
235
- [MODELS.ANTHROPIC_HAIKU]: 8192,
236
-
237
- // OpenAI models
238
- [MODELS.GPT_4]: 8192, // Supports up to 16K, using 8K for safety
239
- [MODELS.GPT_4_MINI]: 16384, // Already at max
240
- 'gpt-4o': 8192, // Supports up to 16K, using 8K
241
- 'gpt-4o-mini': 16384, // Already at max
242
- 'gpt-4-turbo': 8192, // Increased from default
243
- 'gpt-3.5-turbo': 4096, // Smaller model, keep at 4K
244
-
245
- // DeepSeek models
246
- [MODELS.DEEPSEEK_R1]: 8192, // Already at max
247
-
248
- // Phi models - smaller architecture
249
- [MODELS.PHI_4]: 4096, // Increased from 2048
250
-
251
- // Azure AI Foundry models
252
- 'azure-ai-grok3': 8192, // Increased from 4K
253
- 'azure-ai-deepseek-r1': 8192, // Already at max
254
- 'azure-openai-gpt-5': 8192, // Increased from 4K
255
- 'azure-openai-gpt-4': 8192, // Increased from default
256
- 'azure-openai-gpt-4o': 8192, // Increased from default
257
-
258
- // Compaction models - keep moderate for efficiency
259
- [COMPACTION_CONFIG.COMPACTION_MODEL]: 8192,
260
- [COMPACTION_CONFIG.COMPACTION_MODEL_FALLBACK]: 8192,
261
-
262
- // Router model - keep small for fast routing
263
- 'autopilot-model-router': 2048,
264
- };
265
-
266
- return maxOutputTokens[model] || 8192; // Default increased to 8K
267
- }
268
-
269
- /**
270
- * Determine if compaction should be triggered
271
- * @param {number} currentTokens - Current conversation token count (K)
272
- * @param {number} maxOutputTokens - Max tokens model can output (X)
273
- * @param {number} contextWindow - Model's context window size (C)
274
- * @param {number} threshold - Trigger threshold (default 0.8 = 80%)
275
- * @returns {boolean} True if compaction should be triggered
276
- */
277
- shouldTriggerCompaction(currentTokens, maxOutputTokens, contextWindow, threshold = COMPACTION_CONFIG.DEFAULT_THRESHOLD) {
278
- // Validate threshold
279
- if (threshold < COMPACTION_CONFIG.MIN_THRESHOLD || threshold > COMPACTION_CONFIG.MAX_THRESHOLD) {
280
- this.logger?.warn('Invalid compaction threshold, using default', {
281
- provided: threshold,
282
- default: COMPACTION_CONFIG.DEFAULT_THRESHOLD
283
- });
284
- threshold = COMPACTION_CONFIG.DEFAULT_THRESHOLD;
285
- }
286
-
287
- // Calculate: K + X >= threshold * C
288
- const requiredTokens = currentTokens + maxOutputTokens;
289
- const thresholdTokens = threshold * contextWindow;
290
- const shouldTrigger = requiredTokens >= thresholdTokens;
291
-
292
- this.logger?.debug('Compaction trigger check', {
293
- currentTokens,
294
- maxOutputTokens,
295
- contextWindow,
296
- threshold,
297
- requiredTokens,
298
- thresholdTokens,
299
- shouldTrigger,
300
- utilizationPercent: ((requiredTokens / contextWindow) * 100).toFixed(2)
301
- });
302
-
303
- return shouldTrigger;
304
- }
305
-
306
- /**
307
- * Calculate how many tokens to target after compaction
308
- * @param {number} contextWindow - Model's context window size
309
- * @param {number} targetThreshold - Target threshold after compaction (default 85%)
310
- * @returns {number} Target token count after compaction
311
- */
312
- calculateTargetTokenCount(contextWindow, targetThreshold = COMPACTION_CONFIG.MAX_ACCEPTABLE_TOKEN_COUNT_AFTER) {
313
- return Math.floor(contextWindow * targetThreshold);
314
- }
315
-
316
- /**
317
- * Validate that compaction achieved sufficient reduction
318
- * @param {number} originalTokens - Token count before compaction
319
- * @param {number} compactedTokens - Token count after compaction
320
- * @param {number} contextWindow - Model's context window
321
- * @returns {Object} Validation result { valid, reductionPercent, exceedsTarget }
322
- */
323
- validateCompaction(originalTokens, compactedTokens, contextWindow) {
324
- const reductionPercent = ((originalTokens - compactedTokens) / originalTokens) * 100;
325
- const targetTokens = this.calculateTargetTokenCount(contextWindow);
326
- const exceedsTarget = compactedTokens > targetTokens;
327
- const sufficientReduction = reductionPercent >= COMPACTION_CONFIG.MIN_REDUCTION_PERCENTAGE;
328
-
329
- const valid = !exceedsTarget && sufficientReduction;
330
-
331
- this.logger?.info('Compaction validation', {
332
- originalTokens,
333
- compactedTokens,
334
- reductionPercent: reductionPercent.toFixed(2),
335
- targetTokens,
336
- exceedsTarget,
337
- sufficientReduction,
338
- valid
339
- });
340
-
341
- return {
342
- valid,
343
- reductionPercent,
344
- exceedsTarget,
345
- sufficientReduction,
346
- targetTokens,
347
- compactedTokens,
348
- originalTokens
349
- };
350
- }
351
-
352
- /**
353
- * Clear token count cache
354
- * @param {string} model - Optional: clear cache for specific model only
355
- */
356
- clearCache(model = null) {
357
- if (model) {
358
- // Clear cache entries for specific model
359
- for (const [key, value] of this.tokenCache.entries()) {
360
- if (value.model === model) {
361
- this.tokenCache.delete(key);
362
- }
363
- }
364
- this.logger?.debug('Token cache cleared for model', { model });
365
- } else {
366
- // Clear entire cache
367
- this.tokenCache.clear();
368
- this.logger?.debug('Token cache cleared completely');
369
- }
370
- }
371
-
372
- /**
373
- * Get encoder for model (with caching)
374
- * @private
375
- */
376
- async _getEncoder(model) {
377
- // Get tiktoken model name
378
- const tiktokenModel = this.tiktokenModelMap[model] || 'gpt-4';
379
-
380
- // Check if encoder is already cached
381
- if (this.encoders.has(tiktokenModel)) {
382
- return this.encoders.get(tiktokenModel);
383
- }
384
-
385
- // Create new encoder
386
- const encoder = encoding_for_model(tiktokenModel);
387
- this.encoders.set(tiktokenModel, encoder);
388
-
389
- this.logger?.debug('Created tiktoken encoder', {
390
- model,
391
- tiktokenModel
392
- });
393
-
394
- return encoder;
395
- }
396
-
397
- /**
398
- * Fast token estimation (character-based)
399
- * @private
400
- */
401
- _estimateTokens(text) {
402
- if (!text || typeof text !== 'string') {
403
- return 0;
404
- }
405
-
406
- // Use configured estimation ratio
407
- return Math.ceil(text.length / COMPACTION_CONFIG.CHARS_PER_TOKEN_ESTIMATE);
408
- }
409
-
410
- /**
411
- * Get cached token count if available and not expired
412
- * @private
413
- */
414
- _getCachedTokenCount(text, model) {
415
- const cacheKey = this._getCacheKey(text, model);
416
- const cached = this.tokenCache.get(cacheKey);
417
-
418
- if (!cached) {
419
- return null;
420
- }
421
-
422
- // Check if cache entry is expired
423
- const now = Date.now();
424
- if (now - cached.timestamp > COMPACTION_CONFIG.TOKEN_COUNT_CACHE_TTL_MS) {
425
- this.tokenCache.delete(cacheKey);
426
- return null;
427
- }
428
-
429
- return cached.count;
430
- }
431
-
432
- /**
433
- * Cache token count with timestamp
434
- * @private
435
- */
436
- _cacheTokenCount(text, model, count) {
437
- const cacheKey = this._getCacheKey(text, model);
438
-
439
- this.tokenCache.set(cacheKey, {
440
- count,
441
- model,
442
- timestamp: Date.now()
443
- });
444
-
445
- // Prevent cache from growing indefinitely
446
- if (this.tokenCache.size > 1000) {
447
- // Remove oldest entries
448
- const entries = Array.from(this.tokenCache.entries());
449
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
450
-
451
- // Remove oldest 20%
452
- const toRemove = Math.floor(entries.length * 0.2);
453
- for (let i = 0; i < toRemove; i++) {
454
- this.tokenCache.delete(entries[i][0]);
455
- }
456
- }
457
- }
458
-
459
- /**
460
- * Generate cache key for text + model
461
- * @private
462
- */
463
- _getCacheKey(text, model) {
464
- // Use a simple hash for the text to avoid storing full text as key
465
- const hash = this._simpleHash(text);
466
- return `${model}:${hash}`;
467
- }
468
-
469
- /**
470
- * Simple hash function for cache keys
471
- * @private
472
- */
473
- _simpleHash(str) {
474
- let hash = 0;
475
- for (let i = 0; i < str.length; i++) {
476
- const char = str.charCodeAt(i);
477
- hash = ((hash << 5) - hash) + char;
478
- hash = hash & hash; // Convert to 32-bit integer
479
- }
480
- return hash.toString(36);
481
- }
482
-
483
- /**
484
- * Get message formatting overhead for model
485
- * Accounts for role labels, XML tags, etc.
486
- * @private
487
- */
488
- _getMessageFormattingOverhead(model) {
489
- // Different models have different formatting
490
- // Anthropic: ~10 tokens per message (role tags)
491
- // OpenAI: ~5 tokens per message (JSON formatting)
492
-
493
- if (model.includes('anthropic') || model.includes('claude')) {
494
- return 10;
495
- }
496
-
497
- return 5; // Default for OpenAI-style models
498
- }
499
-
500
- /**
501
- * Get conversation-level overhead
502
- * Accounts for system prompts, special tokens, etc.
503
- * @private
504
- */
505
- _getConversationOverhead(messageCount) {
506
- // Base overhead for conversation structure
507
- const baseOverhead = 50;
508
-
509
- // Additional overhead scales with message count
510
- const scalingOverhead = messageCount * 2;
511
-
512
- return baseOverhead + scalingOverhead;
513
- }
514
-
515
- /**
516
- * Clean up resources (close encoders)
517
- */
518
- async cleanup() {
519
- // Free tiktoken encoders
520
- for (const [model, encoder] of this.encoders.entries()) {
521
- try {
522
- encoder.free();
523
- this.logger?.debug('Freed tiktoken encoder', { model });
524
- } catch (error) {
525
- this.logger?.warn('Failed to free encoder', { model, error: error.message });
526
- }
527
- }
528
-
529
- this.encoders.clear();
530
- this.tokenCache.clear();
531
-
532
- this.logger?.info('TokenCountingService cleaned up');
533
- }
534
- }
535
-
536
- export default TokenCountingService;
1
+ function a0_0x404f(_0x51d496,_0x263c5d){_0x51d496=_0x51d496-0xc0;const _0x5eec30=a0_0x5eec();let _0x404fc7=_0x5eec30[_0x51d496];if(a0_0x404f['HhxLgS']===undefined){var _0x3a7935=function(_0x5d9c06){const _0x30f995='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x39deaf='',_0x9dc902='';for(let _0x222e5b=0x0,_0x270674,_0x49e451,_0x3f6edb=0x0;_0x49e451=_0x5d9c06['charAt'](_0x3f6edb++);~_0x49e451&&(_0x270674=_0x222e5b%0x4?_0x270674*0x40+_0x49e451:_0x49e451,_0x222e5b++%0x4)?_0x39deaf+=String['fromCharCode'](0xff&_0x270674>>(-0x2*_0x222e5b&0x6)):0x0){_0x49e451=_0x30f995['indexOf'](_0x49e451);}for(let _0x4c3474=0x0,_0x3d2053=_0x39deaf['length'];_0x4c3474<_0x3d2053;_0x4c3474++){_0x9dc902+='%'+('00'+_0x39deaf['charCodeAt'](_0x4c3474)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x9dc902);};a0_0x404f['XSmPsr']=_0x3a7935,a0_0x404f['pyVWJb']={},a0_0x404f['HhxLgS']=!![];}const _0x37a080=_0x5eec30[0x0],_0x5412f7=_0x51d496+_0x37a080,_0x1ad6bd=a0_0x404f['pyVWJb'][_0x5412f7];return!_0x1ad6bd?(_0x404fc7=a0_0x404f['XSmPsr'](_0x404fc7),a0_0x404f['pyVWJb'][_0x5412f7]=_0x404fc7):_0x404fc7=_0x1ad6bd,_0x404fc7;}const a0_0x419ff6=a0_0x404f;function a0_0x5eec(){const _0x1c650d=['BwvZC2fNzq','odyZndmYrgLzDNnV','zNjVBq','q09nuefdveLptL9nt0rfta','revgqvvmvf9usfjfu0Hpteq','y29UDgvUDa','DgLRDg9Rzw5nB2rLBe1HCa','mMj0C3LxzG','z2v0tw9KzwXdB250zxH0v2LUzg93','mJbntvL3s1a','mti5nJy3ovbYCxfHvG','odeWndeZnKPlv3ztCG','qu5usfjpueLdx0HbsuTv','tuLox1jfrfvdveLptL9qrvjdru5uquDf','ve9lru5Fq09vtLrFq0fdsevFvfrmx01t','Dg9tDhjPBMC','zgvIDwC','y2XLyxjdywnOzq','mJaXmZu4ntn2yK1kwNm','C3rYAw5N','BgvUz3rO','C2v0','y2XLyw51Ca','mJmWotKXmfHUuMPHCq','q09nuefdveLptL9nt0rftf9gquXmqKfdsW','vg9Rzw5dB3vUDgLUz1nLCNzPy2uGy2XLyw5Lzcb1Ca','z3b0ltq','revfufnfruTFuJe','zgvSzxrL','mtyWnZG0mwnnqM1krG','AxnbCNjHEq','q0fdseve','x2DLDevUy29Kzxi','y2XLyxi','z2v0tw9KzwXnyxHpDxrWDxruB2TLBNm','vgLRDg9Rzw4Gzw5JB2rPBMCGzMfPBgvKlcbMywXSAw5NigjHy2SGDg8Gzxn0Aw1HDgLVBG','x2DLDenHy2HLzfrVA2vUq291BNq','Dg9Rzw5dywnOzq','AgfZ','q29TCgfJDgLVBIb0CMLNz2vYignOzwnR','Bw9KzwW','x2vZDgLTyxrLvg9Rzw5Z','y2XHDwrL','rMfPBgvKihrVigzYzwuGzw5JB2rLCG','A2v5CW','DMfSAwrHDgvdB21Wywn0Aw9U','x3nPBxbSzuHHC2G','y291BNruB2TLBNm','vg9Rzw4Gy2fJAguGy2XLyxjLzcbJB21WBgv0zwX5','r1buxZrFtuLosq','zMXVB3i','q0HbuLnFuevsx1rps0vox0vtveLnqvrf','zw5JB2rLCNm','x2DLDenHy2HLs2v5','q29UDMvYC2f0Aw9UihrVA2vUignVDw50','tufyx0fdq0vqvefctevFve9lru5Fq09vtLrFquzurvi','otu2otyZwgvuCe9k','mtbjEuv6uge','zw50CMLLCW','x2DLDenVBNzLCNnHDgLVBK92zxjOzwfK','ueHjxZq','zw5JB2rL','x2DLDe1LC3nHz2vgB3jTyxr0Aw5Nt3zLCMHLywq','Bg9Nz2vY','rvnusu1bveve','z2v0','qundvvjbveu','DgLTzxn0yw1W'];a0_0x5eec=function(){return _0x1c650d;};return a0_0x5eec();}(function(_0x411312,_0x3a51c8){const _0x415921=a0_0x404f,_0x3ca8aa=_0x411312();while(!![]){try{const _0x25653c=-parseInt(_0x415921(0xe8))/0x1+parseInt(_0x415921(0xe5))/0x2*(-parseInt(_0x415921(0xfb))/0x3)+parseInt(_0x415921(0xdf))/0x4*(-parseInt(_0x415921(0xd3))/0x5)+-parseInt(_0x415921(0xf5))/0x6+-parseInt(_0x415921(0xd2))/0x7+-parseInt(_0x415921(0xe9))/0x8+-parseInt(_0x415921(0xf0))/0x9*(-parseInt(_0x415921(0xe7))/0xa);if(_0x25653c===_0x3a51c8)break;else _0x3ca8aa['push'](_0x3ca8aa['shift']());}catch(_0x36f724){_0x3ca8aa['push'](_0x3ca8aa['shift']());}}}(a0_0x5eec,0xa4efd));import{encoding_for_model}from'tiktoken';import{MODELS,COMPACTION_CONFIG,TOKEN_COUNTING_MODES}from'../utilities/constants.js';class TokenCountingService{constructor(_0x39deaf){const _0x2cc6b8=a0_0x404f;this['logger']=_0x39deaf,this[_0x2cc6b8(0x103)]=new Map(),this['encoders']=new Map(),this['tiktokenModelMap']={[MODELS['ANTHROPIC_SONNET']]:_0x2cc6b8(0xf8),[MODELS['ANTHROPIC_OPUS']]:'gpt-4',[MODELS[_0x2cc6b8(0xea)]]:_0x2cc6b8(0xf8),[MODELS['GPT_4']]:'gpt-4',[MODELS[_0x2cc6b8(0xcb)]]:_0x2cc6b8(0xf8),'gpt-4o':'gpt-4','gpt-4o-mini':'gpt-4','gpt-4-turbo':_0x2cc6b8(0xf8),'gpt-3.5-turbo':_0x2cc6b8(0xf8),[MODELS[_0x2cc6b8(0xf9)]]:'gpt-4',[MODELS[_0x2cc6b8(0xd6)]]:_0x2cc6b8(0xf8),'azure-ai-grok3':'gpt-4','azure-ai-deepseek-r1':'gpt-4','azure-openai-gpt-5':_0x2cc6b8(0xf8),'azure-openai-gpt-4':'gpt-4','azure-openai-gpt-4o':'gpt-4',[COMPACTION_CONFIG['COMPACTION_MODEL']]:_0x2cc6b8(0xf8),[COMPACTION_CONFIG['COMPACTION_MODEL_FALLBACK']]:'gpt-4'},this[_0x2cc6b8(0xd9)]?.['info']('TokenCountingService\x20initialized',{'supportedModels':Object[_0x2cc6b8(0xc6)](this['tiktokenModelMap'])['length'],'cacheEnabled':!![]});}async[a0_0x419ff6(0xc9)](_0x9dc902,_0x222e5b,_0x270674=TOKEN_COUNTING_MODES[a0_0x419ff6(0xdc)]){const _0x1cb57c=a0_0x419ff6;if(!_0x9dc902||typeof _0x9dc902!==_0x1cb57c(0xf1))return 0x0;if(_0x270674===TOKEN_COUNTING_MODES[_0x1cb57c(0xfd)]){const _0x49e451=this[_0x1cb57c(0x102)](_0x9dc902,_0x222e5b);if(_0x49e451!==null)return _0x49e451;}if(_0x270674===TOKEN_COUNTING_MODES[_0x1cb57c(0xda)])return this[_0x1cb57c(0xc3)](_0x9dc902);try{const _0x3f6edb=await this[_0x1cb57c(0xfe)](_0x222e5b),_0x4c3474=_0x3f6edb[_0x1cb57c(0xd7)](_0x9dc902),_0x3d2053=_0x4c3474[_0x1cb57c(0xf2)];return this['_cacheTokenCount'](_0x9dc902,_0x222e5b,_0x3d2053),_0x3d2053;}catch(_0x1c382f){return this[_0x1cb57c(0xd9)]?.['warn'](_0x1cb57c(0x101),{'model':_0x222e5b,'error':_0x1c382f[_0x1cb57c(0xde)]}),this[_0x1cb57c(0xc3)](_0x9dc902);}}async['estimateConversationTokens'](_0x41d8fd,_0x58dc07,_0x27198e=TOKEN_COUNTING_MODES[a0_0x419ff6(0xdc)]){const _0x4a9adb=a0_0x419ff6;if(!Array[_0x4a9adb(0xfc)](_0x41d8fd)||_0x41d8fd['length']===0x0)return 0x0;let _0x2150ce=0x0;for(const _0x4ec58c of _0x41d8fd){if(!_0x4ec58c[_0x4a9adb(0xe3)])continue;const _0x23ccbc=this['_getMessageFormattingOverhead'](_0x58dc07),_0x22c251=await this['countTokens'](_0x4ec58c[_0x4a9adb(0xe3)],_0x58dc07,_0x27198e);_0x2150ce+=_0x22c251+_0x23ccbc;}const _0x4eba1e=this[_0x4a9adb(0xd5)](_0x41d8fd[_0x4a9adb(0xf2)]);return _0x2150ce+=_0x4eba1e,this[_0x4a9adb(0xd9)]?.[_0x4a9adb(0xee)](_0x4a9adb(0xd0),{'model':_0x58dc07,'messageCount':_0x41d8fd['length'],'totalTokens':_0x2150ce,'mode':_0x27198e}),_0x2150ce;}[a0_0x419ff6(0xe6)](_0xd35df7){const _0x2c9133=a0_0x419ff6,_0xe85724={[MODELS['ANTHROPIC_SONNET']]:0x30d40,[MODELS['ANTHROPIC_OPUS']]:0x30d40,[MODELS['ANTHROPIC_HAIKU']]:0x30d40,[MODELS['GPT_4']]:0x1f400,[MODELS[_0x2c9133(0xcb)]]:0x1f400,'gpt-4o':0x1f400,'gpt-4o-mini':0x1f400,'gpt-4-turbo':0x1f400,'gpt-3.5-turbo':0x4000,[MODELS['DEEPSEEK_R1']]:0x1f400,[MODELS[_0x2c9133(0xd6)]]:0x4000,'azure-ai-grok3':0x1f400,'azure-ai-deepseek-r1':0x1f400,'azure-openai-gpt-5':0x1f400,'azure-openai-gpt-4':0x1f400,'azure-openai-gpt-4o':0x1f400,[COMPACTION_CONFIG[_0x2c9133(0xe1)]]:0x1f400,[COMPACTION_CONFIG['COMPACTION_MODEL_FALLBACK']]:0x1f400,'autopilot-model-router':0x4000},_0x298507=_0xe85724[_0xd35df7];if(!_0x298507)return this['logger']?.['warn']('Unknown model context window, using default',{'model':_0xd35df7,'defaultWindow':0x1f400}),0x1f400;return _0x298507;}[a0_0x419ff6(0x100)](_0x5c029a){const _0x16bbae=a0_0x419ff6,_0x5d790b={[MODELS['ANTHROPIC_SONNET']]:0x2000,[MODELS['ANTHROPIC_OPUS']]:0x2000,[MODELS['ANTHROPIC_HAIKU']]:0x2000,[MODELS['GPT_4']]:0x2000,[MODELS[_0x16bbae(0xcb)]]:0x4000,'gpt-4o':0x2000,'gpt-4o-mini':0x4000,'gpt-4-turbo':0x2000,'gpt-3.5-turbo':0x1000,[MODELS[_0x16bbae(0xf9)]]:0x2000,[MODELS[_0x16bbae(0xd6)]]:0x1000,'azure-ai-grok3':0x2000,'azure-ai-deepseek-r1':0x2000,'azure-openai-gpt-5':0x2000,'azure-openai-gpt-4':0x2000,'azure-openai-gpt-4o':0x2000,[COMPACTION_CONFIG[_0x16bbae(0xe1)]]:0x2000,[COMPACTION_CONFIG[_0x16bbae(0xf6)]]:0x2000,'autopilot-model-router':0x800};return _0x5d790b[_0x5c029a]||0x2000;}['shouldTriggerCompaction'](_0x530ea9,_0x41b5e1,_0xa3e45e,_0x364ad0=COMPACTION_CONFIG[a0_0x419ff6(0xe2)]){const _0x25d1fb=a0_0x419ff6;(_0x364ad0<COMPACTION_CONFIG['MIN_THRESHOLD']||_0x364ad0>COMPACTION_CONFIG['MAX_THRESHOLD'])&&(this['logger']?.['warn']('Invalid compaction threshold, using default',{'provided':_0x364ad0,'default':COMPACTION_CONFIG[_0x25d1fb(0xe2)]}),_0x364ad0=COMPACTION_CONFIG['DEFAULT_THRESHOLD']);const requiredTokens=_0x530ea9+_0x41b5e1,_0x44eb76=_0x364ad0*_0xa3e45e,_0x2c3133=requiredTokens>=_0x44eb76;return this['logger']?.[_0x25d1fb(0xee)](_0x25d1fb(0xc1),{'currentTokens':_0x530ea9,'maxOutputTokens':_0x41b5e1,'contextWindow':_0xa3e45e,'threshold':_0x364ad0,'requiredTokens':requiredTokens,'thresholdTokens':_0x44eb76,'shouldTrigger':_0x2c3133,'utilizationPercent':(requiredTokens/_0xa3e45e*0x64)['toFixed'](0x2)}),_0x2c3133;}['calculateTargetTokenCount'](_0x536319,_0x38ef74=COMPACTION_CONFIG[a0_0x419ff6(0xd1)]){return Math['floor'](_0x536319*_0x38ef74);}[a0_0x419ff6(0xc7)](_0x16930f,_0x138bd6,_0xb49a31){const _0x57eaf6=a0_0x419ff6,_0x3cf57a=(_0x16930f-_0x138bd6)/_0x16930f*0x64,_0x38c339=this['calculateTargetTokenCount'](_0xb49a31),_0x4bfe0a=_0x138bd6>_0x38c339,_0x2c6499=_0x3cf57a>=COMPACTION_CONFIG[_0x57eaf6(0xeb)],_0x296906=!_0x4bfe0a&&_0x2c6499;return this[_0x57eaf6(0xd9)]?.['info']('Compaction\x20validation',{'originalTokens':_0x16930f,'compactedTokens':_0x138bd6,'reductionPercent':_0x3cf57a['toFixed'](0x2),'targetTokens':_0x38c339,'exceedsTarget':_0x4bfe0a,'sufficientReduction':_0x2c6499,'valid':_0x296906}),{'valid':_0x296906,'reductionPercent':_0x3cf57a,'exceedsTarget':_0x4bfe0a,'sufficientReduction':_0x2c6499,'targetTokens':_0x38c339,'compactedTokens':_0x138bd6,'originalTokens':_0x16930f};}[a0_0x419ff6(0xef)](_0x50784f=null){const _0x1cb217=a0_0x419ff6;if(_0x50784f){for(const [_0x10271a,_0x413250]of this['tokenCache'][_0x1cb217(0xd4)]()){_0x413250[_0x1cb217(0xc2)]===_0x50784f&&this[_0x1cb217(0x103)][_0x1cb217(0xfa)](_0x10271a);}this[_0x1cb217(0xd9)]?.['debug']('Token\x20cache\x20cleared\x20for\x20model',{'model':_0x50784f});}else this['tokenCache'][_0x1cb217(0xff)](),this[_0x1cb217(0xd9)]?.['debug'](_0x1cb217(0xca));}async['_getEncoder'](_0x12aff2){const _0x4bdac8=a0_0x419ff6,_0x1d349d=this[_0x4bdac8(0xe4)][_0x12aff2]||_0x4bdac8(0xf8);if(this['encoders'][_0x4bdac8(0xc0)](_0x1d349d))return this[_0x4bdac8(0xce)]['get'](_0x1d349d);const _0x25ddea=encoding_for_model(_0x1d349d);return this[_0x4bdac8(0xce)][_0x4bdac8(0xf3)](_0x1d349d,_0x25ddea),this['logger']?.[_0x4bdac8(0xee)]('Created\x20tiktoken\x20encoder',{'model':_0x12aff2,'tiktokenModel':_0x1d349d}),_0x25ddea;}['_estimateTokens'](_0xdda81){const _0x2d68d6=a0_0x419ff6;if(!_0xdda81||typeof _0xdda81!==_0x2d68d6(0xf1))return 0x0;return Math['ceil'](_0xdda81['length']/COMPACTION_CONFIG[_0x2d68d6(0xcd)]);}[a0_0x419ff6(0x102)](_0x23772f,_0x5be4dc){const _0x4896c9=a0_0x419ff6,_0x42546b=this[_0x4896c9(0xcf)](_0x23772f,_0x5be4dc),_0x2cb0d8=this[_0x4896c9(0x103)][_0x4896c9(0xdb)](_0x42546b);if(!_0x2cb0d8)return null;const _0x4393c3=Date['now']();if(_0x4393c3-_0x2cb0d8[_0x4896c9(0xdd)]>COMPACTION_CONFIG[_0x4896c9(0xec)])return this[_0x4896c9(0x103)]['delete'](_0x42546b),null;return _0x2cb0d8['count'];}['_cacheTokenCount'](_0x58da30,_0x53d01f,_0x70e739){const _0x15f332=a0_0x419ff6,_0x5f573c=this[_0x15f332(0xcf)](_0x58da30,_0x53d01f);this['tokenCache']['set'](_0x5f573c,{'count':_0x70e739,'model':_0x53d01f,'timestamp':Date['now']()});if(this[_0x15f332(0x103)]['size']>0x3e8){const _0x32d242=Array[_0x15f332(0xe0)](this[_0x15f332(0x103)]['entries']());_0x32d242['sort']((_0x47349c,_0x223dda)=>_0x47349c[0x1][_0x15f332(0xdd)]-_0x223dda[0x1][_0x15f332(0xdd)]);const _0x4b0396=Math[_0x15f332(0xcc)](_0x32d242[_0x15f332(0xf2)]*0.2);for(let _0x99e9ca=0x0;_0x99e9ca<_0x4b0396;_0x99e9ca++){this[_0x15f332(0x103)]['delete'](_0x32d242[_0x99e9ca][0x0]);}}}[a0_0x419ff6(0xcf)](_0x46b3cd,_0x23dfed){const _0x48466b=a0_0x419ff6,_0x532736=this[_0x48466b(0xc8)](_0x46b3cd);return _0x23dfed+':'+_0x532736;}[a0_0x419ff6(0xc8)](_0x2d0af0){const _0xafd7d8=a0_0x419ff6;let _0x58e0a3=0x0;for(let _0x3344a7=0x0;_0x3344a7<_0x2d0af0['length'];_0x3344a7++){const _0x27c70e=_0x2d0af0['charCodeAt'](_0x3344a7);_0x58e0a3=(_0x58e0a3<<0x5)-_0x58e0a3+_0x27c70e,_0x58e0a3=_0x58e0a3&_0x58e0a3;}return _0x58e0a3[_0xafd7d8(0xed)](0x24);}[a0_0x419ff6(0xd8)](_0x1aba9c){const _0x1cc5ab=a0_0x419ff6;if(_0x1aba9c['includes']('anthropic')||_0x1aba9c['includes'](_0x1cc5ab(0xc4)))return 0xa;return 0x5;}['_getConversationOverhead'](_0x381b21){const _0x1c664a=0x32,_0x5aec32=_0x381b21*0x2;return _0x1c664a+_0x5aec32;}async[a0_0x419ff6(0xf4)](){const _0x287860=a0_0x419ff6;for(const [_0x12788a,_0x1866bd]of this[_0x287860(0xce)]['entries']()){try{_0x1866bd['free'](),this['logger']?.[_0x287860(0xee)]('Freed\x20tiktoken\x20encoder',{'model':_0x12788a});}catch(_0x3b1dd5){this['logger']?.['warn'](_0x287860(0xc5),{'model':_0x12788a,'error':_0x3b1dd5[_0x287860(0xde)]});}}this['encoders']['clear'](),this['tokenCache'][_0x287860(0xff)](),this['logger']?.['info'](_0x287860(0xf7));}}export default TokenCountingService;