claude-usage-tracker
Version:
Advanced analytics for Claude Code usage with cost optimization, conversation length analysis, and rate limit tracking
267 lines • 12.2 kB
JavaScript
import chalk from "chalk";
import { MODEL_PRICING } from "./config.js";
import { TASK_MODEL_MAPPING } from "./recommendation-config.js";
import { getModelPricing } from "./config-loader.js";
export class ModelAdvisor {
taskPatterns = {
code_generation: [
/write\s+(a\s+)?function/i,
/create\s+(a\s+)?(class|component|module)/i,
/implement\s+/i,
/generate\s+(code|script)/i,
/build\s+(a\s+)?(feature|api|endpoint)/i,
/make\s+(a\s+)?(function|class|component)/i,
],
debugging: [
/debug/i,
/fix\s+(this\s+)?(bug|error|issue)/i,
/why\s+(is|isn't|does|doesn't)/i,
/what's\s+wrong/i,
/not\s+working/i,
/error/i,
/exception/i,
/stack\s+trace/i,
],
code_review: [
/review\s+(this\s+)?code/i,
/look\s+at\s+(this\s+)?code/i,
/check\s+(this\s+)?implementation/i,
/feedback\s+on/i,
/improve\s+(this\s+)?code/i,
/optimize\s+(this\s+)?code/i,
],
documentation: [
/document/i,
/write\s+(a\s+)?readme/i,
/explain\s+(how\s+)?this/i,
/add\s+comments/i,
/write\s+docs/i,
/create\s+documentation/i,
],
architecture: [
/architecture/i,
/design\s+pattern/i,
/system\s+design/i,
/structure\s+(this\s+)?project/i,
/best\s+practices/i,
/organize\s+(the\s+)?code/i,
],
complex_analysis: [
/analyze\s+(this\s+)?(complex|large|entire)/i,
/understand\s+(this\s+)?(complex|large|entire)/i,
/reverse\s+engineer/i,
/performance\s+analysis/i,
/security\s+analysis/i,
/comprehensive\s+review/i,
],
simple_query: [
/what\s+is/i,
/how\s+do\s+i/i,
/can\s+you\s+tell\s+me/i,
/quick\s+question/i,
/simple\s+question/i,
/just\s+wondering/i,
],
refactoring: [
/refactor/i,
/clean\s+up/i,
/reorganize/i,
/restructure/i,
/improve\s+structure/i,
/make\s+(this\s+)?cleaner/i,
],
};
modelRecommendations = TASK_MODEL_MAPPING;
classifyTask(prompt) {
let bestMatch = {
taskType: "simple_query",
confidence: 0.1,
reasoning: "Default classification - no strong pattern match",
};
for (const [taskType, patterns] of Object.entries(this.taskPatterns)) {
let matches = 0;
const matchDetails = [];
for (const pattern of patterns) {
if (pattern.test(prompt)) {
matches++;
matchDetails.push(pattern.source);
}
}
if (matches > 0) {
const confidence = Math.min(0.95, 0.3 + matches * 0.2);
if (confidence > bestMatch.confidence) {
bestMatch = {
taskType: taskType,
confidence,
reasoning: `Matched ${matches} pattern(s): ${matchDetails.slice(0, 2).join(", ")}`,
};
}
}
}
// Additional context analysis
const wordCount = prompt.split(/\s+/).length;
const hasCodeBlocks = /```/.test(prompt);
const hasMultipleQuestions = (prompt.match(/\?/g) || []).length > 2;
// Adjust confidence based on context
if (wordCount > 200 && bestMatch.taskType === "simple_query") {
bestMatch.taskType = "complex_analysis";
bestMatch.confidence = 0.6;
bestMatch.reasoning += " (adjusted for length)";
}
if (hasCodeBlocks && bestMatch.confidence < 0.7) {
bestMatch.confidence = Math.min(0.9, bestMatch.confidence + 0.2);
bestMatch.reasoning += " (code context boost)";
}
if (hasMultipleQuestions && bestMatch.taskType === "simple_query") {
bestMatch.taskType = "complex_analysis";
bestMatch.confidence = Math.min(0.8, bestMatch.confidence + 0.1);
bestMatch.reasoning += " (multiple questions detected)";
}
return bestMatch;
}
getModelRecommendation(classification) {
const baseRec = this.modelRecommendations[classification.taskType];
const recommendedModelPricing = MODEL_PRICING[baseRec.model];
// Find a reasonable alternative model (Sonnet if we're using Opus, Opus if we're using Sonnet)
// Use the config loader directly since Object.keys on the proxy doesn't work in tests
const allModels = Object.keys(getModelPricing());
let alternativeModel;
if (baseRec.model.includes("opus")) {
alternativeModel =
allModels.find((m) => m.includes("sonnet")) || allModels[0];
}
else if (baseRec.model.includes("sonnet")) {
alternativeModel =
allModels.find((m) => m.includes("opus")) || allModels[0];
}
else {
alternativeModel =
allModels.find((m) => m !== baseRec.model) || allModels[0];
}
const alternativeModelPricing = MODEL_PRICING[alternativeModel];
// Ensure we have valid pricing data
if (!recommendedModelPricing || !alternativeModelPricing) {
return {
recommendedModel: baseRec.model,
confidence: classification.confidence,
reasoning: classification.reasoning +
" (pricing data unavailable for cost comparison)",
};
}
// Estimate token usage (rough approximation)
const estimatedTokens = this.estimateTokenUsage(classification.taskType);
const recommendedModelCost = (estimatedTokens.input / 1_000_000) * recommendedModelPricing.input +
(estimatedTokens.output / 1_000_000) * recommendedModelPricing.output;
const alternativeModelCost = (estimatedTokens.input / 1_000_000) * alternativeModelPricing.input +
(estimatedTokens.output / 1_000_000) * alternativeModelPricing.output;
// Calculate cost savings relative to the more expensive option
const costSavings = Math.abs(alternativeModelCost - recommendedModelCost);
const reasoning = this.getReasoningForTask(classification.taskType, baseRec.model);
// Alternative model suggestion
const alternativeTradeoffs = this.getAlternativeTradeoffs(classification.taskType, alternativeModel);
return {
recommendedModel: baseRec.model,
confidence: Math.min(0.95, baseRec.confidence * classification.confidence),
costSavings: costSavings,
reasoning,
alternativeModel: {
model: alternativeModel,
tradeoffs: alternativeTradeoffs,
},
};
}
estimateTokenUsage(taskType) {
const estimates = {
code_generation: { input: 2000, output: 3000 },
debugging: { input: 3000, output: 2000 },
code_review: { input: 4000, output: 2500 },
documentation: { input: 1500, output: 2000 },
architecture: { input: 2500, output: 4000 },
complex_analysis: { input: 5000, output: 3500 },
simple_query: { input: 500, output: 800 },
refactoring: { input: 3000, output: 3500 },
};
return estimates[taskType] || estimates.simple_query;
}
getReasoningForTask(taskType, model) {
const reasons = {
code_generation: {
"claude-3.5-sonnet-20241022": "Sonnet 4 excels at code generation with 78% cost savings. Quality is excellent for most coding tasks.",
"claude-opus-4-20250514": "Opus 4 for the most complex algorithms or when you need the highest code quality.",
},
debugging: {
"claude-3.5-sonnet-20241022": "Sonnet 4 can handle most debugging tasks effectively with significant cost savings.",
"claude-opus-4-20250514": "Opus 4 recommended for complex debugging - better at understanding intricate code relationships.",
},
code_review: {
"claude-3.5-sonnet-20241022": "Sonnet 4 provides thorough code reviews with excellent cost efficiency.",
"claude-opus-4-20250514": "Opus 4 for critical code reviews where you need the deepest analysis.",
},
documentation: {
"claude-3.5-sonnet-20241022": "Sonnet 4 is perfect for documentation - clear writing with major cost savings.",
"claude-opus-4-20250514": "Opus 4 overkill for most documentation tasks.",
},
architecture: {
"claude-3.5-sonnet-20241022": "Sonnet 4 can handle many architecture discussions cost-effectively.",
"claude-opus-4-20250514": "Opus 4 recommended for complex system design - better strategic thinking.",
},
complex_analysis: {
"claude-3.5-sonnet-20241022": "Sonnet 4 may miss nuances in complex analysis.",
"claude-opus-4-20250514": "Opus 4 essential for deep analysis - superior reasoning and context understanding.",
},
simple_query: {
"claude-3.5-sonnet-20241022": "Sonnet 4 perfect for straightforward questions - massive cost savings.",
"claude-opus-4-20250514": "Opus 4 wasteful for simple queries.",
},
refactoring: {
"claude-3.5-sonnet-20241022": "Sonnet 4 excellent for refactoring with great cost efficiency.",
"claude-opus-4-20250514": "Opus 4 for complex refactoring of large codebases.",
},
};
const taskReasons = reasons[taskType];
return (taskReasons?.[String(model)] ||
`${String(model).includes("sonnet") ? "Sonnet 4 for cost efficiency" : "Opus 4 for maximum capability"}`);
}
getAlternativeTradeoffs(_taskType, alternativeModel) {
if (String(alternativeModel).includes("opus")) {
return "Higher cost but maximum reasoning capability and nuance detection";
}
else {
return "78% cost savings but may miss some nuances in very complex tasks";
}
}
formatRecommendation(classification, recommendation) {
let output = "";
output += chalk.blue.bold("🤖 Model Recommendation\n");
output += `${chalk.gray("─".repeat(50))}
`;
// Task classification
output +=
chalk.cyan("Task Type: ") +
chalk.white(classification.taskType.replace("_", " ")) +
chalk.gray(` (${(classification.confidence * 100).toFixed(0)}% confidence)\n`);
output += chalk.gray(`Reasoning: ${classification.reasoning}\n\n`);
// Recommendation
const modelName = String(recommendation.recommendedModel);
const confidenceColor = recommendation.confidence > 0.8
? chalk.green
: recommendation.confidence > 0.6
? chalk.yellow
: chalk.red;
output += chalk.green.bold(`✅ Recommended: ${modelName}\n`);
output += confidenceColor(`Confidence: ${(recommendation.confidence * 100).toFixed(0)}%\n`);
if (recommendation.costSavings) {
output += chalk.green(`💰 Estimated savings: $${recommendation.costSavings.toFixed(4)} per conversation\n`);
}
output += chalk.white(`${recommendation.reasoning}\n\n`);
// Alternative
if (recommendation.alternativeModel) {
const altName = String(recommendation.alternativeModel.model);
output += chalk.yellow(`⚡ Alternative: ${altName}
`);
output += chalk.gray(`${recommendation.alternativeModel.tradeoffs}\n`);
}
return output;
}
}
//# sourceMappingURL=model-advisor.js.map