tachibot-mcp 2.0.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/.env.example +260 -0
- package/CHANGELOG.md +54 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +54 -0
- package/Dockerfile +36 -0
- package/LICENSE +644 -0
- package/README.md +201 -0
- package/SECURITY.md +95 -0
- package/dist/personality/komaai-expressions.js +12 -0
- package/dist/profiles/balanced.json +33 -0
- package/dist/profiles/code_focus.json +33 -0
- package/dist/profiles/full.json +33 -0
- package/dist/profiles/minimal.json +33 -0
- package/dist/profiles/research_power.json +33 -0
- package/dist/scripts/build-profiles.js +46 -0
- package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
- package/dist/src/application/services/focus/FocusTool.service.js +109 -0
- package/dist/src/application/services/focus/ModeRegistry.js +46 -0
- package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
- package/dist/src/application/services/focus/modes/status.mode.js +50 -0
- package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
- package/dist/src/collaborative-orchestrator.js +391 -0
- package/dist/src/config/model-constants.js +188 -0
- package/dist/src/config/model-defaults.js +57 -0
- package/dist/src/config/model-preferences.js +382 -0
- package/dist/src/config/timeout-config.js +130 -0
- package/dist/src/config.js +173 -0
- package/dist/src/domain/interfaces/IFocusMode.js +5 -0
- package/dist/src/domain/interfaces/IProvider.js +6 -0
- package/dist/src/domain/interfaces/ITool.js +5 -0
- package/dist/src/focus-deep.js +245 -0
- package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
- package/dist/src/mcp-client.js +90 -0
- package/dist/src/memory/index.js +17 -0
- package/dist/src/memory/memory-config.js +135 -0
- package/dist/src/memory/memory-interface.js +174 -0
- package/dist/src/memory/memory-manager.js +383 -0
- package/dist/src/memory/providers/devlog-provider.js +385 -0
- package/dist/src/memory/providers/hybrid-provider.js +399 -0
- package/dist/src/memory/providers/local-provider.js +388 -0
- package/dist/src/memory/providers/mem0-provider.js +337 -0
- package/dist/src/modes/architect.js +477 -0
- package/dist/src/modes/auditor.js +362 -0
- package/dist/src/modes/challenger.js +841 -0
- package/dist/src/modes/code-reviewer.js +382 -0
- package/dist/src/modes/commit-guardian.js +424 -0
- package/dist/src/modes/documentation-writer.js +572 -0
- package/dist/src/modes/scout.js +587 -0
- package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
- package/dist/src/modes/shared/helpers/index.js +17 -0
- package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
- package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
- package/dist/src/modes/test-architect.js +767 -0
- package/dist/src/modes/verifier.js +378 -0
- package/dist/src/monitoring/performance-monitor.js +435 -0
- package/dist/src/optimization/batch-executor.js +121 -0
- package/dist/src/optimization/context-pruner.js +196 -0
- package/dist/src/optimization/cost-monitor.js +338 -0
- package/dist/src/optimization/index.js +65 -0
- package/dist/src/optimization/model-router.js +264 -0
- package/dist/src/optimization/result-cache.js +114 -0
- package/dist/src/optimization/token-optimizer.js +257 -0
- package/dist/src/optimization/token-tracker.js +118 -0
- package/dist/src/orchestrator-instructions.js +128 -0
- package/dist/src/orchestrator-lite.js +139 -0
- package/dist/src/orchestrator.js +191 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
- package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
- package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
- package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
- package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
- package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
- package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
- package/dist/src/profiles/balanced.js +37 -0
- package/dist/src/profiles/code_focus.js +37 -0
- package/dist/src/profiles/debug_intensive.js +59 -0
- package/dist/src/profiles/full.js +37 -0
- package/dist/src/profiles/minimal.js +37 -0
- package/dist/src/profiles/research_code.js +59 -0
- package/dist/src/profiles/research_power.js +37 -0
- package/dist/src/profiles/types.js +5 -0
- package/dist/src/profiles/workflow_builder.js +53 -0
- package/dist/src/prompt-engineer-lite.js +78 -0
- package/dist/src/prompt-engineer.js +399 -0
- package/dist/src/reasoning-chain.js +508 -0
- package/dist/src/sequential-thinking.js +291 -0
- package/dist/src/server-diagnostic.js +74 -0
- package/dist/src/server-raw.js +158 -0
- package/dist/src/server-simple.js +58 -0
- package/dist/src/server.js +514 -0
- package/dist/src/session/session-logger.js +617 -0
- package/dist/src/session/session-manager.js +571 -0
- package/dist/src/session/session-tools.js +400 -0
- package/dist/src/tools/advanced-modes.js +200 -0
- package/dist/src/tools/claude-integration.js +356 -0
- package/dist/src/tools/consolidated/ai-router.js +174 -0
- package/dist/src/tools/consolidated/ai-tool.js +48 -0
- package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
- package/dist/src/tools/consolidated/environment-detector.js +80 -0
- package/dist/src/tools/consolidated/index.js +50 -0
- package/dist/src/tools/consolidated/search-tool.js +110 -0
- package/dist/src/tools/consolidated/workflow-tool.js +238 -0
- package/dist/src/tools/gemini-tools.js +329 -0
- package/dist/src/tools/grok-enhanced.js +376 -0
- package/dist/src/tools/grok-tools.js +299 -0
- package/dist/src/tools/lmstudio-tools.js +223 -0
- package/dist/src/tools/openai-tools.js +498 -0
- package/dist/src/tools/openrouter-tools.js +317 -0
- package/dist/src/tools/optimized-wrapper.js +204 -0
- package/dist/src/tools/perplexity-tools.js +294 -0
- package/dist/src/tools/pingpong-tool.js +343 -0
- package/dist/src/tools/qwen-wrapper.js +74 -0
- package/dist/src/tools/tool-router.js +444 -0
- package/dist/src/tools/unified-ai-provider.js +260 -0
- package/dist/src/tools/workflow-runner.js +425 -0
- package/dist/src/tools/workflow-validator-tool.js +107 -0
- package/dist/src/types.js +23 -0
- package/dist/src/utils/input-validator.js +130 -0
- package/dist/src/utils/model-router.js +91 -0
- package/dist/src/utils/progress-stream.js +255 -0
- package/dist/src/utils/provider-router.js +88 -0
- package/dist/src/utils/smart-api-client.js +146 -0
- package/dist/src/utils/table-builder.js +218 -0
- package/dist/src/utils/timestamp-formatter.js +134 -0
- package/dist/src/utils/tool-compressor.js +257 -0
- package/dist/src/utils/tool-config.js +201 -0
- package/dist/src/validators/dependency-graph-validator.js +147 -0
- package/dist/src/validators/interpolation-validator.js +222 -0
- package/dist/src/validators/output-usage-validator.js +151 -0
- package/dist/src/validators/syntax-validator.js +102 -0
- package/dist/src/validators/tool-registry-validator.js +123 -0
- package/dist/src/validators/tool-types.js +97 -0
- package/dist/src/validators/types.js +8 -0
- package/dist/src/validators/workflow-validator.js +134 -0
- package/dist/src/visualizer-lite.js +42 -0
- package/dist/src/visualizer.js +179 -0
- package/dist/src/workflows/circuit-breaker.js +199 -0
- package/dist/src/workflows/custom-workflows.js +451 -0
- package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
- package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
- package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
- package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
- package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
- package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
- package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
- package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
- package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
- package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
- package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
- package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
- package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
- package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
- package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
- package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
- package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
- package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
- package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
- package/dist/src/workflows/fallback-strategies.js +373 -0
- package/dist/src/workflows/message-queue.js +455 -0
- package/dist/src/workflows/model-router.js +189 -0
- package/dist/src/workflows/orchestrator-examples.js +174 -0
- package/dist/src/workflows/orchestrator-integration.js +200 -0
- package/dist/src/workflows/self-healing.js +524 -0
- package/dist/src/workflows/tool-mapper.js +407 -0
- package/dist/src/workflows/tool-orchestrator.js +796 -0
- package/dist/src/workflows/workflow-engine.js +573 -0
- package/dist/src/workflows/workflow-parser.js +283 -0
- package/dist/src/workflows/workflow-types.js +95 -0
- package/dist/src/workflows.js +568 -0
- package/dist/test-workflow-file-output.js +93 -0
- package/docs/API_KEYS.md +570 -0
- package/docs/CLAUDE_CODE_SETUP.md +181 -0
- package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
- package/docs/CONFIGURATION.md +745 -0
- package/docs/FOCUS_MODES.md +240 -0
- package/docs/INSTALLATION_BOTH.md +145 -0
- package/docs/TERMS.md +352 -0
- package/docs/TOOLS_REFERENCE.md +1622 -0
- package/docs/TOOL_PARAMETERS.md +496 -0
- package/docs/TOOL_PROFILES.md +236 -0
- package/docs/WORKFLOWS.md +987 -0
- package/docs/WORKFLOW_OUTPUT.md +198 -0
- package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
- package/docs/workflows/design-brainstorm.md +335 -0
- package/package.json +97 -0
- package/profiles/balanced.json +37 -0
- package/profiles/code_focus.json +37 -0
- package/profiles/debug_intensive.json +34 -0
- package/profiles/full.json +37 -0
- package/profiles/minimal.json +37 -0
- package/profiles/research_power.json +37 -0
- package/profiles/workflow_builder.json +37 -0
- package/smithery.yaml +66 -0
- package/start.sh +8 -0
- package/tools.config.json +81 -0
- package/tsconfig.json +18 -0
- package/workflows/accessibility-code-audit.yaml +92 -0
- package/workflows/code-architecture-review.yaml +202 -0
- package/workflows/code-review.yaml +142 -0
- package/workflows/core/iterative-problem-solver.yaml +283 -0
- package/workflows/creative-brainstorm-yaml.yaml +215 -0
- package/workflows/pingpong.yaml +141 -0
- package/workflows/system/README.md +412 -0
- package/workflows/system/challenger.yaml +175 -0
- package/workflows/system/scout.yaml +164 -0
- package/workflows/system/verifier.yaml +133 -0
- package/workflows/ultra-creative-brainstorm.yaml +318 -0
- package/workflows/ux-research-flow.yaml +92 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export class ContextPruner {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.strategies = new Map([
|
|
4
|
+
['embedding-similarity', this.pruneByEmbedding.bind(this)],
|
|
5
|
+
['keyword', this.pruneByKeyword.bind(this)],
|
|
6
|
+
['length', this.pruneByLength.bind(this)],
|
|
7
|
+
['hybrid', this.pruneHybrid.bind(this)]
|
|
8
|
+
]);
|
|
9
|
+
}
|
|
10
|
+
prune(text, options) {
|
|
11
|
+
const strategy = this.strategies.get(options.strategy) || this.pruneByLength.bind(this);
|
|
12
|
+
const pruned = strategy(text, options);
|
|
13
|
+
const originalTokens = this.estimateTokens(text);
|
|
14
|
+
const prunedTokens = this.estimateTokens(pruned);
|
|
15
|
+
const tokensReduced = originalTokens - prunedTokens;
|
|
16
|
+
const reductionRate = tokensReduced / originalTokens;
|
|
17
|
+
return {
|
|
18
|
+
original: text,
|
|
19
|
+
pruned,
|
|
20
|
+
tokensReduced,
|
|
21
|
+
reductionRate,
|
|
22
|
+
preservedSections: this.identifyPreservedSections(text, pruned)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
pruneByEmbedding(text, options) {
|
|
26
|
+
const sentences = this.splitIntoSentences(text);
|
|
27
|
+
const scores = this.calculateSentenceImportance(sentences);
|
|
28
|
+
const sortedSentences = sentences
|
|
29
|
+
.map((sentence, index) => ({ sentence, score: scores[index], index }))
|
|
30
|
+
.sort((a, b) => b.score - a.score);
|
|
31
|
+
let result = [];
|
|
32
|
+
let currentTokens = 0;
|
|
33
|
+
for (const item of sortedSentences) {
|
|
34
|
+
const sentenceTokens = this.estimateTokens(item.sentence);
|
|
35
|
+
if (currentTokens + sentenceTokens <= options.maxTokens) {
|
|
36
|
+
result.push(item.sentence);
|
|
37
|
+
currentTokens += sentenceTokens;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Preserve order
|
|
41
|
+
result = result.sort((a, b) => {
|
|
42
|
+
const indexA = sentences.indexOf(a);
|
|
43
|
+
const indexB = sentences.indexOf(b);
|
|
44
|
+
return indexA - indexB;
|
|
45
|
+
});
|
|
46
|
+
return result.join(' ');
|
|
47
|
+
}
|
|
48
|
+
pruneByKeyword(text, options) {
|
|
49
|
+
const keywords = this.extractKeywords(text);
|
|
50
|
+
const sentences = this.splitIntoSentences(text);
|
|
51
|
+
const scoredSentences = sentences.map(sentence => {
|
|
52
|
+
const score = keywords.reduce((sum, keyword) => {
|
|
53
|
+
return sum + (sentence.toLowerCase().includes(keyword.toLowerCase()) ? 1 : 0);
|
|
54
|
+
}, 0);
|
|
55
|
+
return { sentence, score };
|
|
56
|
+
});
|
|
57
|
+
scoredSentences.sort((a, b) => b.score - a.score);
|
|
58
|
+
let result = [];
|
|
59
|
+
let currentTokens = 0;
|
|
60
|
+
for (const item of scoredSentences) {
|
|
61
|
+
const sentenceTokens = this.estimateTokens(item.sentence);
|
|
62
|
+
if (currentTokens + sentenceTokens <= options.maxTokens) {
|
|
63
|
+
result.push(item.sentence);
|
|
64
|
+
currentTokens += sentenceTokens;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result.join(' ');
|
|
68
|
+
}
|
|
69
|
+
pruneByLength(text, options) {
|
|
70
|
+
const targetLength = options.maxTokens * 4; // Rough character estimate
|
|
71
|
+
if (text.length <= targetLength) {
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
const sections = this.splitIntoSections(text);
|
|
75
|
+
let result = '';
|
|
76
|
+
for (const section of sections) {
|
|
77
|
+
if (result.length + section.length <= targetLength) {
|
|
78
|
+
result += section + '\n\n';
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const remaining = targetLength - result.length;
|
|
82
|
+
result += section.substring(0, remaining);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result.trim();
|
|
87
|
+
}
|
|
88
|
+
pruneHybrid(text, options) {
|
|
89
|
+
// Combine multiple strategies
|
|
90
|
+
const embeddingWeight = 0.4;
|
|
91
|
+
const keywordWeight = 0.3;
|
|
92
|
+
const recencyWeight = 0.3;
|
|
93
|
+
const sentences = this.splitIntoSentences(text);
|
|
94
|
+
const embeddingScores = this.calculateSentenceImportance(sentences);
|
|
95
|
+
const keywords = this.extractKeywords(text);
|
|
96
|
+
const finalScores = sentences.map((sentence, index) => {
|
|
97
|
+
const embeddingScore = embeddingScores[index] * embeddingWeight;
|
|
98
|
+
const keywordScore = keywords.reduce((sum, keyword) => {
|
|
99
|
+
return sum + (sentence.toLowerCase().includes(keyword.toLowerCase()) ? 1 : 0);
|
|
100
|
+
}, 0) * keywordWeight;
|
|
101
|
+
const recencyScore = (index / sentences.length) * recencyWeight;
|
|
102
|
+
return {
|
|
103
|
+
sentence,
|
|
104
|
+
score: embeddingScore + keywordScore + recencyScore,
|
|
105
|
+
index
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
finalScores.sort((a, b) => b.score - a.score);
|
|
109
|
+
let result = [];
|
|
110
|
+
let currentTokens = 0;
|
|
111
|
+
for (const item of finalScores) {
|
|
112
|
+
const sentenceTokens = this.estimateTokens(item.sentence);
|
|
113
|
+
if (currentTokens + sentenceTokens <= options.maxTokens) {
|
|
114
|
+
result.push(item);
|
|
115
|
+
currentTokens += sentenceTokens;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Preserve original order
|
|
119
|
+
result.sort((a, b) => a.index - b.index);
|
|
120
|
+
return result.map(item => item.sentence).join(' ');
|
|
121
|
+
}
|
|
122
|
+
splitIntoSentences(text) {
|
|
123
|
+
return text.match(/[^.!?]+[.!?]+/g) || [text];
|
|
124
|
+
}
|
|
125
|
+
splitIntoSections(text) {
|
|
126
|
+
return text.split(/\n\n+/).filter(s => s.trim());
|
|
127
|
+
}
|
|
128
|
+
calculateSentenceImportance(sentences) {
|
|
129
|
+
// Simplified importance calculation
|
|
130
|
+
return sentences.map(sentence => {
|
|
131
|
+
let score = 0;
|
|
132
|
+
// Length factor
|
|
133
|
+
if (sentence.length > 50 && sentence.length < 200)
|
|
134
|
+
score += 0.3;
|
|
135
|
+
// Contains numbers or data
|
|
136
|
+
if (/\d+/.test(sentence))
|
|
137
|
+
score += 0.2;
|
|
138
|
+
// Contains technical terms (simplified)
|
|
139
|
+
const technicalTerms = ['function', 'class', 'method', 'api', 'error', 'bug', 'feature'];
|
|
140
|
+
technicalTerms.forEach(term => {
|
|
141
|
+
if (sentence.toLowerCase().includes(term))
|
|
142
|
+
score += 0.1;
|
|
143
|
+
});
|
|
144
|
+
// Question or conclusion indicators
|
|
145
|
+
if (sentence.includes('?') || sentence.includes('therefore') || sentence.includes('conclusion')) {
|
|
146
|
+
score += 0.3;
|
|
147
|
+
}
|
|
148
|
+
return Math.min(score, 1);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
extractKeywords(text) {
|
|
152
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
153
|
+
const wordFreq = new Map();
|
|
154
|
+
const stopWords = new Set([
|
|
155
|
+
'the', 'is', 'at', 'which', 'on', 'a', 'an', 'as', 'are', 'was', 'were',
|
|
156
|
+
'been', 'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
157
|
+
'could', 'should', 'may', 'might', 'must', 'can', 'this', 'that', 'these',
|
|
158
|
+
'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'what', 'which', 'who',
|
|
159
|
+
'when', 'where', 'why', 'how', 'all', 'each', 'every', 'both', 'few', 'more',
|
|
160
|
+
'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same',
|
|
161
|
+
'so', 'than', 'too', 'very', 'just', 'but', 'for', 'with', 'about', 'into',
|
|
162
|
+
'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'of', 'in'
|
|
163
|
+
]);
|
|
164
|
+
for (const word of words) {
|
|
165
|
+
if (word.length > 3 && !stopWords.has(word)) {
|
|
166
|
+
wordFreq.set(word, (wordFreq.get(word) || 0) + 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Array.from(wordFreq.entries())
|
|
170
|
+
.sort((a, b) => b[1] - a[1])
|
|
171
|
+
.slice(0, 10)
|
|
172
|
+
.map(([word]) => word);
|
|
173
|
+
}
|
|
174
|
+
estimateTokens(text) {
|
|
175
|
+
return Math.ceil(text.length / 4);
|
|
176
|
+
}
|
|
177
|
+
identifyPreservedSections(original, pruned) {
|
|
178
|
+
const prunedSentences = this.splitIntoSentences(pruned);
|
|
179
|
+
return prunedSentences.slice(0, 3).map(s => s.substring(0, 50) + '...');
|
|
180
|
+
}
|
|
181
|
+
getRecommendedStrategy(text, requirements) {
|
|
182
|
+
const length = text.length;
|
|
183
|
+
const hasCode = /```[\s\S]*?```/.test(text);
|
|
184
|
+
const hasNumbers = /\d+/.test(text);
|
|
185
|
+
if (hasCode) {
|
|
186
|
+
return 'keyword'; // Preserve code blocks
|
|
187
|
+
}
|
|
188
|
+
if (length > 10000) {
|
|
189
|
+
return 'hybrid'; // Use multiple strategies for long text
|
|
190
|
+
}
|
|
191
|
+
if (hasNumbers || requirements.preserveData) {
|
|
192
|
+
return 'embedding-similarity'; // Better at preserving important data
|
|
193
|
+
}
|
|
194
|
+
return 'length'; // Simple and fast for short text
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Monitor - Real-time tracking and limits for API usage
|
|
3
|
+
* Part of Phase 1C implementation for cost control
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from "events";
|
|
6
|
+
export class CostMonitor extends EventEmitter {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
// Cost limits
|
|
10
|
+
this.limits = {
|
|
11
|
+
hourly: 1.0, // $1 per hour
|
|
12
|
+
daily: 10.0, // $10 per day
|
|
13
|
+
monthly: 300.0, // $300 per month
|
|
14
|
+
};
|
|
15
|
+
// Warning thresholds (percentage of limit)
|
|
16
|
+
this.thresholds = {
|
|
17
|
+
warning: 0.75, // 75% of limit
|
|
18
|
+
critical: 0.9, // 90% of limit
|
|
19
|
+
};
|
|
20
|
+
// Usage tracking
|
|
21
|
+
this.usage = new Map();
|
|
22
|
+
this.records = [];
|
|
23
|
+
this.modelUsage = new Map();
|
|
24
|
+
// Model pricing (updated Nov 2025 - includes GPT-5)
|
|
25
|
+
this.modelCosts = new Map([
|
|
26
|
+
// GPT-5 Models (Official pricing as of Nov 2025)
|
|
27
|
+
[
|
|
28
|
+
"gpt-5-nano",
|
|
29
|
+
{ model: "gpt-5-nano", inputCost: 0.00005, outputCost: 0.0004 },
|
|
30
|
+
], // ULTRA CHEAP!
|
|
31
|
+
[
|
|
32
|
+
"gpt-5-mini",
|
|
33
|
+
{ model: "gpt-5-mini", inputCost: 0.00025, outputCost: 0.002 },
|
|
34
|
+
],
|
|
35
|
+
["gpt-5", { model: "gpt-5", inputCost: 0.00125, outputCost: 0.01 }],
|
|
36
|
+
[
|
|
37
|
+
"gpt-5-chat-latest",
|
|
38
|
+
{ model: "gpt-5-chat-latest", inputCost: 0.00125, outputCost: 0.01 },
|
|
39
|
+
],
|
|
40
|
+
// Existing models
|
|
41
|
+
[
|
|
42
|
+
"gemini-2.5-flash",
|
|
43
|
+
{ model: "gemini-2.5-flash", inputCost: 0.000075, outputCost: 0.0003 },
|
|
44
|
+
],
|
|
45
|
+
["gpt-5-nano", { model: "gpt-5-nano", inputCost: 0.00005, outputCost: 0.0004 }],
|
|
46
|
+
[
|
|
47
|
+
"gpt-5-mini",
|
|
48
|
+
{ model: "gpt-5-mini", inputCost: 0.00025, outputCost: 0.002 },
|
|
49
|
+
],
|
|
50
|
+
["gpt-5", { model: "gpt-5", inputCost: 0.0025, outputCost: 0.01 }],
|
|
51
|
+
[
|
|
52
|
+
"perplexity-sonar-pro",
|
|
53
|
+
{ model: "perplexity-sonar-pro", inputCost: 0.006, outputCost: 0.006 },
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
"claude-3.5-sonnet",
|
|
57
|
+
{ model: "claude-3.5-sonnet", inputCost: 0.003, outputCost: 0.015 },
|
|
58
|
+
],
|
|
59
|
+
["grok-4", { model: "grok-4", inputCost: 0.005, outputCost: 0.015 }],
|
|
60
|
+
]);
|
|
61
|
+
// Clean up old records periodically
|
|
62
|
+
setInterval(() => this.cleanupOldRecords(), 60 * 60 * 1000); // Every hour
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set custom limits
|
|
66
|
+
*/
|
|
67
|
+
setLimits(limits) {
|
|
68
|
+
this.limits = { ...this.limits, ...limits };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Calculate cost for a request
|
|
72
|
+
*/
|
|
73
|
+
calculateCost(model, inputTokens, outputTokens) {
|
|
74
|
+
const normalizedModel = this.normalizeModelId(model);
|
|
75
|
+
const pricing = this.modelCosts.get(normalizedModel);
|
|
76
|
+
if (!pricing) {
|
|
77
|
+
console.warn(`Unknown model pricing for: ${model}, using default`);
|
|
78
|
+
return (inputTokens * 0.001 + outputTokens * 0.002) / 1000; // Default pricing
|
|
79
|
+
}
|
|
80
|
+
return ((inputTokens * pricing.inputCost + outputTokens * pricing.outputCost) /
|
|
81
|
+
1000);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Track usage for a request
|
|
85
|
+
*/
|
|
86
|
+
async trackUsage(model, inputTokens, outputTokens, requestId, userId) {
|
|
87
|
+
const normalizedModel = this.normalizeModelId(model);
|
|
88
|
+
const cost = this.calculateCost(model, inputTokens, outputTokens);
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
// Check if request would exceed limits
|
|
91
|
+
const hourKey = this.getTimeKey("hour");
|
|
92
|
+
const dayKey = this.getTimeKey("day");
|
|
93
|
+
const monthKey = this.getTimeKey("month");
|
|
94
|
+
const hourlyUsage = (this.usage.get(`hour:${hourKey}`) || 0) + cost;
|
|
95
|
+
const dailyUsage = (this.usage.get(`day:${dayKey}`) || 0) + cost;
|
|
96
|
+
const monthlyUsage = (this.usage.get(`month:${monthKey}`) || 0) + cost;
|
|
97
|
+
// Check hard limits
|
|
98
|
+
if (hourlyUsage > this.limits.hourly) {
|
|
99
|
+
const alert = {
|
|
100
|
+
type: "limit_exceeded",
|
|
101
|
+
message: `Hourly limit exceeded. Current: $${hourlyUsage.toFixed(2)}, Limit: $${this.limits.hourly}`,
|
|
102
|
+
currentCost: hourlyUsage,
|
|
103
|
+
limit: this.limits.hourly,
|
|
104
|
+
timeframe: "hourly",
|
|
105
|
+
};
|
|
106
|
+
this.emit("limitExceeded", alert);
|
|
107
|
+
return { allowed: false, cost, alert };
|
|
108
|
+
}
|
|
109
|
+
if (dailyUsage > this.limits.daily) {
|
|
110
|
+
const alert = {
|
|
111
|
+
type: "limit_exceeded",
|
|
112
|
+
message: `Daily limit exceeded. Current: $${dailyUsage.toFixed(2)}, Limit: $${this.limits.daily}`,
|
|
113
|
+
currentCost: dailyUsage,
|
|
114
|
+
limit: this.limits.daily,
|
|
115
|
+
timeframe: "daily",
|
|
116
|
+
};
|
|
117
|
+
this.emit("limitExceeded", alert);
|
|
118
|
+
return { allowed: false, cost, alert };
|
|
119
|
+
}
|
|
120
|
+
// Record the usage
|
|
121
|
+
const record = {
|
|
122
|
+
timestamp: now,
|
|
123
|
+
model: normalizedModel,
|
|
124
|
+
inputTokens,
|
|
125
|
+
outputTokens,
|
|
126
|
+
cost,
|
|
127
|
+
requestId,
|
|
128
|
+
userId,
|
|
129
|
+
};
|
|
130
|
+
this.records.push(record);
|
|
131
|
+
this.usage.set(`hour:${hourKey}`, hourlyUsage);
|
|
132
|
+
this.usage.set(`day:${dayKey}`, dailyUsage);
|
|
133
|
+
this.usage.set(`month:${monthKey}`, monthlyUsage);
|
|
134
|
+
// Update model usage stats
|
|
135
|
+
const modelStats = this.modelUsage.get(normalizedModel) || {
|
|
136
|
+
count: 0,
|
|
137
|
+
cost: 0,
|
|
138
|
+
};
|
|
139
|
+
modelStats.count++;
|
|
140
|
+
modelStats.cost += cost;
|
|
141
|
+
this.modelUsage.set(normalizedModel, modelStats);
|
|
142
|
+
// Check warning thresholds
|
|
143
|
+
let alert;
|
|
144
|
+
if (hourlyUsage > this.limits.hourly * this.thresholds.critical) {
|
|
145
|
+
alert = {
|
|
146
|
+
type: "critical",
|
|
147
|
+
message: `Critical: Approaching hourly limit (${((hourlyUsage / this.limits.hourly) * 100).toFixed(0)}%)`,
|
|
148
|
+
currentCost: hourlyUsage,
|
|
149
|
+
limit: this.limits.hourly,
|
|
150
|
+
timeframe: "hourly",
|
|
151
|
+
};
|
|
152
|
+
this.emit("criticalAlert", alert);
|
|
153
|
+
}
|
|
154
|
+
else if (hourlyUsage > this.limits.hourly * this.thresholds.warning) {
|
|
155
|
+
alert = {
|
|
156
|
+
type: "warning",
|
|
157
|
+
message: `Warning: ${((hourlyUsage / this.limits.hourly) * 100).toFixed(0)}% of hourly limit used`,
|
|
158
|
+
currentCost: hourlyUsage,
|
|
159
|
+
limit: this.limits.hourly,
|
|
160
|
+
timeframe: "hourly",
|
|
161
|
+
};
|
|
162
|
+
this.emit("warningAlert", alert);
|
|
163
|
+
}
|
|
164
|
+
return { allowed: true, cost, alert };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if a request would be allowed
|
|
168
|
+
*/
|
|
169
|
+
async checkRequest(model, estimatedTokens) {
|
|
170
|
+
// Estimate 40% input, 60% output
|
|
171
|
+
const inputTokens = estimatedTokens * 0.4;
|
|
172
|
+
const outputTokens = estimatedTokens * 0.6;
|
|
173
|
+
const estimatedCost = this.calculateCost(model, inputTokens, outputTokens);
|
|
174
|
+
const hourKey = this.getTimeKey("hour");
|
|
175
|
+
const dayKey = this.getTimeKey("day");
|
|
176
|
+
const hourlyUsage = this.usage.get(`hour:${hourKey}`) || 0;
|
|
177
|
+
const dailyUsage = this.usage.get(`day:${dayKey}`) || 0;
|
|
178
|
+
// Check if it would exceed limits
|
|
179
|
+
if (hourlyUsage + estimatedCost > this.limits.hourly) {
|
|
180
|
+
return {
|
|
181
|
+
allowed: false,
|
|
182
|
+
estimatedCost,
|
|
183
|
+
requiresConfirmation: true,
|
|
184
|
+
warning: `Would exceed hourly limit ($${this.limits.hourly})`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (dailyUsage + estimatedCost > this.limits.daily) {
|
|
188
|
+
return {
|
|
189
|
+
allowed: false,
|
|
190
|
+
estimatedCost,
|
|
191
|
+
requiresConfirmation: true,
|
|
192
|
+
warning: `Would exceed daily limit ($${this.limits.daily})`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Check if confirmation needed
|
|
196
|
+
const requiresConfirmation = estimatedCost > 0.1; // Confirm for requests > $0.10
|
|
197
|
+
return {
|
|
198
|
+
allowed: true,
|
|
199
|
+
estimatedCost,
|
|
200
|
+
requiresConfirmation,
|
|
201
|
+
warning: requiresConfirmation
|
|
202
|
+
? `High cost request: $${estimatedCost.toFixed(2)}`
|
|
203
|
+
: undefined,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get usage report
|
|
208
|
+
*/
|
|
209
|
+
getUsageReport() {
|
|
210
|
+
const hourKey = this.getTimeKey("hour");
|
|
211
|
+
const dayKey = this.getTimeKey("day");
|
|
212
|
+
const monthKey = this.getTimeKey("month");
|
|
213
|
+
const hourlyUsage = this.usage.get(`hour:${hourKey}`) || 0;
|
|
214
|
+
const dailyUsage = this.usage.get(`day:${dayKey}`) || 0;
|
|
215
|
+
const monthlyUsage = this.usage.get(`month:${monthKey}`) || 0;
|
|
216
|
+
// Get top models by cost
|
|
217
|
+
const topModels = Array.from(this.modelUsage.entries())
|
|
218
|
+
.map(([model, stats]) => ({
|
|
219
|
+
model,
|
|
220
|
+
usage: stats.count,
|
|
221
|
+
cost: stats.cost,
|
|
222
|
+
}))
|
|
223
|
+
.sort((a, b) => b.cost - a.cost)
|
|
224
|
+
.slice(0, 5);
|
|
225
|
+
// Generate alerts
|
|
226
|
+
const alerts = [];
|
|
227
|
+
if (hourlyUsage > this.limits.hourly * this.thresholds.warning) {
|
|
228
|
+
alerts.push({
|
|
229
|
+
type: hourlyUsage > this.limits.hourly * this.thresholds.critical
|
|
230
|
+
? "critical"
|
|
231
|
+
: "warning",
|
|
232
|
+
message: `${((hourlyUsage / this.limits.hourly) * 100).toFixed(0)}% of hourly limit used`,
|
|
233
|
+
currentCost: hourlyUsage,
|
|
234
|
+
limit: this.limits.hourly,
|
|
235
|
+
timeframe: "hourly",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (dailyUsage > this.limits.daily * this.thresholds.warning) {
|
|
239
|
+
alerts.push({
|
|
240
|
+
type: dailyUsage > this.limits.daily * this.thresholds.critical
|
|
241
|
+
? "critical"
|
|
242
|
+
: "warning",
|
|
243
|
+
message: `${((dailyUsage / this.limits.daily) * 100).toFixed(0)}% of daily limit used`,
|
|
244
|
+
currentCost: dailyUsage,
|
|
245
|
+
limit: this.limits.daily,
|
|
246
|
+
timeframe: "daily",
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
hourly: {
|
|
251
|
+
used: hourlyUsage,
|
|
252
|
+
limit: this.limits.hourly,
|
|
253
|
+
remaining: Math.max(0, this.limits.hourly - hourlyUsage),
|
|
254
|
+
percentage: (hourlyUsage / this.limits.hourly) * 100,
|
|
255
|
+
},
|
|
256
|
+
daily: {
|
|
257
|
+
used: dailyUsage,
|
|
258
|
+
limit: this.limits.daily,
|
|
259
|
+
remaining: Math.max(0, this.limits.daily - dailyUsage),
|
|
260
|
+
percentage: (dailyUsage / this.limits.daily) * 100,
|
|
261
|
+
},
|
|
262
|
+
monthly: {
|
|
263
|
+
used: monthlyUsage,
|
|
264
|
+
limit: this.limits.monthly,
|
|
265
|
+
remaining: Math.max(0, this.limits.monthly - monthlyUsage),
|
|
266
|
+
percentage: (monthlyUsage / this.limits.monthly) * 100,
|
|
267
|
+
},
|
|
268
|
+
topModels,
|
|
269
|
+
alerts,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
normalizeModelId(model) {
|
|
273
|
+
switch (model) {
|
|
274
|
+
case "gpt5":
|
|
275
|
+
return "gpt-5";
|
|
276
|
+
case "gpt5_mini":
|
|
277
|
+
return "gpt-5-mini";
|
|
278
|
+
case "gpt5_nano":
|
|
279
|
+
return "gpt-5-nano";
|
|
280
|
+
default:
|
|
281
|
+
return model;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get time key for usage tracking
|
|
286
|
+
*/
|
|
287
|
+
getTimeKey(period) {
|
|
288
|
+
const now = new Date();
|
|
289
|
+
switch (period) {
|
|
290
|
+
case "hour":
|
|
291
|
+
return now.toISOString().slice(0, 13); // YYYY-MM-DDTHH
|
|
292
|
+
case "day":
|
|
293
|
+
return now.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
294
|
+
case "month":
|
|
295
|
+
return now.toISOString().slice(0, 7); // YYYY-MM
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Clean up old records
|
|
300
|
+
*/
|
|
301
|
+
cleanupOldRecords() {
|
|
302
|
+
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
|
|
303
|
+
// Remove records older than 24 hours
|
|
304
|
+
this.records = this.records.filter((r) => r.timestamp > oneDayAgo);
|
|
305
|
+
// Clean up old usage keys
|
|
306
|
+
const currentHour = this.getTimeKey("hour");
|
|
307
|
+
const currentDay = this.getTimeKey("day");
|
|
308
|
+
const currentMonth = this.getTimeKey("month");
|
|
309
|
+
for (const [key] of this.usage.entries()) {
|
|
310
|
+
if (key.startsWith("hour:") && !key.includes(currentHour.slice(0, 10))) {
|
|
311
|
+
this.usage.delete(key);
|
|
312
|
+
}
|
|
313
|
+
else if (key.startsWith("day:") && !key.includes(currentMonth)) {
|
|
314
|
+
this.usage.delete(key);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Reset all usage data
|
|
320
|
+
*/
|
|
321
|
+
reset() {
|
|
322
|
+
this.usage.clear();
|
|
323
|
+
this.records = [];
|
|
324
|
+
this.modelUsage.clear();
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Export usage data for analysis
|
|
328
|
+
*/
|
|
329
|
+
exportUsageData() {
|
|
330
|
+
return {
|
|
331
|
+
records: this.records,
|
|
332
|
+
summary: this.getUsageReport(),
|
|
333
|
+
modelBreakdown: this.modelUsage,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Export singleton instance
|
|
338
|
+
export const costMonitor = new CostMonitor();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimization Module - Exports for Phase 1 improvements
|
|
3
|
+
* Provides smart model routing, token optimization, and cost monitoring
|
|
4
|
+
*/
|
|
5
|
+
// Import singletons
|
|
6
|
+
import { modelRouter } from './model-router.js';
|
|
7
|
+
import { tokenOptimizer } from './token-optimizer.js';
|
|
8
|
+
import { costMonitor } from './cost-monitor.js';
|
|
9
|
+
// Re-export all types and classes
|
|
10
|
+
export { SmartModelRouter, modelRouter, ModelTier } from './model-router.js';
|
|
11
|
+
export { TokenOptimizer, tokenOptimizer } from './token-optimizer.js';
|
|
12
|
+
export { CostMonitor, costMonitor } from './cost-monitor.js';
|
|
13
|
+
/**
|
|
14
|
+
* Initialize all optimization modules
|
|
15
|
+
*/
|
|
16
|
+
export function initializeOptimizations(config) {
|
|
17
|
+
// Set cost limits if provided
|
|
18
|
+
if (config?.costLimits) {
|
|
19
|
+
costMonitor.setLimits(config.costLimits);
|
|
20
|
+
}
|
|
21
|
+
// Listen for cost alerts
|
|
22
|
+
costMonitor.on('warningAlert', (alert) => {
|
|
23
|
+
console.warn(`⚠️ Cost Warning: ${alert.message}`);
|
|
24
|
+
});
|
|
25
|
+
costMonitor.on('criticalAlert', (alert) => {
|
|
26
|
+
console.error(`🚨 Cost Critical: ${alert.message}`);
|
|
27
|
+
});
|
|
28
|
+
costMonitor.on('limitExceeded', (alert) => {
|
|
29
|
+
console.error(`🛑 Cost Limit Exceeded: ${alert.message}`);
|
|
30
|
+
});
|
|
31
|
+
console.error('✅ Optimizations initialized:');
|
|
32
|
+
console.error(' • Smart model routing enabled');
|
|
33
|
+
console.error(' • Token optimization active');
|
|
34
|
+
console.error(' • Cost monitoring configured');
|
|
35
|
+
return {
|
|
36
|
+
modelRouter,
|
|
37
|
+
tokenOptimizer,
|
|
38
|
+
costMonitor,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get optimization statistics
|
|
43
|
+
*/
|
|
44
|
+
export function getOptimizationStats() {
|
|
45
|
+
const tokenMetrics = tokenOptimizer.getMetrics();
|
|
46
|
+
const costReport = costMonitor.getUsageReport();
|
|
47
|
+
return {
|
|
48
|
+
tokenOptimization: {
|
|
49
|
+
cacheHitRate: tokenMetrics.cacheHitRate,
|
|
50
|
+
compressionRatio: tokenMetrics.compressionRatio,
|
|
51
|
+
tokensSaved: tokenMetrics.totalSaved,
|
|
52
|
+
batchesProcessed: tokenMetrics.batchesProcessed,
|
|
53
|
+
},
|
|
54
|
+
costMonitoring: {
|
|
55
|
+
hourlyUsage: costReport.hourly.used,
|
|
56
|
+
dailyUsage: costReport.daily.used,
|
|
57
|
+
monthlyUsage: costReport.monthly.used,
|
|
58
|
+
topModels: costReport.topModels,
|
|
59
|
+
},
|
|
60
|
+
recommendations: [
|
|
61
|
+
...tokenMetrics.recommendations,
|
|
62
|
+
...costReport.alerts.map((a) => a.message),
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|