elsabro 2.2.0 → 3.7.0
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/README.md +668 -20
- package/agents/elsabro-orchestrator.md +113 -0
- package/bin/install.js +0 -0
- package/commands/elsabro/execute.md +223 -46
- package/commands/elsabro/start.md +34 -0
- package/commands/elsabro/verify-work.md +29 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/hooks/confirm-destructive.sh +145 -0
- package/hooks/hooks-config.json +81 -0
- package/hooks/lint-check.sh +238 -0
- package/hooks/post-edit-test.sh +189 -0
- package/package.json +5 -3
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-tests.md +1171 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/error-contracts.md +3102 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/parallel-worktrees.md +293 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/scripts/setup-parallel-worktrees.sh +319 -0
- package/skills/memory-update.md +207 -0
- package/skills/review.md +331 -0
- package/skills/techdebt.md +289 -0
- package/skills/tutor.md +219 -0
- package/templates/.planning/notes/.gitkeep +0 -0
- package/templates/CLAUDE.md.template +48 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/error-handling-config.json +79 -2
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mistakes.md.template +52 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/patterns.md.template +114 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- package/templates/voice-commands-config.json +658 -0
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
# Cost Tracking & Optimization (v3.3)
|
|
2
|
+
|
|
3
|
+
Sistema de monitoreo de costos, presupuestos y optimización automática de selección de modelos.
|
|
4
|
+
|
|
5
|
+
## Arquitectura
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ COST TRACKING SYSTEM │
|
|
10
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
13
|
+
│ │ TOKEN TRACKER │ │
|
|
14
|
+
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
|
15
|
+
│ │ │ Per-Request Metering │ │ │
|
|
16
|
+
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
|
|
17
|
+
│ │ │ │ Input │ │ Output │ │ Cache │ │ │ │
|
|
18
|
+
│ │ │ │ Tokens │ │ Tokens │ │ Hits │ │ │ │
|
|
19
|
+
│ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │
|
|
20
|
+
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
|
21
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
22
|
+
│ │ │
|
|
23
|
+
│ ▼ │
|
|
24
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
25
|
+
│ │ BUDGET MANAGER │ │
|
|
26
|
+
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
27
|
+
│ │ │ Session │ │ Daily │ │ Monthly │ │ │
|
|
28
|
+
│ │ │ Budget │ │ Budget │ │ Budget │ │ │
|
|
29
|
+
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
30
|
+
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
|
|
31
|
+
│ │ │ Alerts: warning (80%) │ critical (95%) │ hard_limit │ │ │
|
|
32
|
+
│ │ └───────────────────────────────────────────────────────────┘ │ │
|
|
33
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
34
|
+
│ │ │
|
|
35
|
+
│ ▼ │
|
|
36
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
37
|
+
│ │ MODEL OPTIMIZER │ │
|
|
38
|
+
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
|
39
|
+
│ │ │ Task Analysis → Model Selection → Cost Prediction │ │ │
|
|
40
|
+
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
|
|
41
|
+
│ │ │ │ Simple │ → │ Haiku │ │ $0.001 │ │ │ │
|
|
42
|
+
│ │ │ │ Medium │ → │ Sonnet │ │ $0.015 │ │ │ │
|
|
43
|
+
│ │ │ │ Complex │ → │ Opus │ │ $0.075 │ │ │ │
|
|
44
|
+
│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │
|
|
45
|
+
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
|
46
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
47
|
+
│ │
|
|
48
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## TokenTracker
|
|
54
|
+
|
|
55
|
+
### API Principal
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface TokenUsage {
|
|
59
|
+
input_tokens: number;
|
|
60
|
+
output_tokens: number;
|
|
61
|
+
cache_creation_tokens?: number;
|
|
62
|
+
cache_read_tokens?: number;
|
|
63
|
+
total_tokens: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface UsageRecord {
|
|
67
|
+
id: string;
|
|
68
|
+
timestamp: string;
|
|
69
|
+
model: string;
|
|
70
|
+
agent?: string;
|
|
71
|
+
task?: string;
|
|
72
|
+
flow?: string;
|
|
73
|
+
usage: TokenUsage;
|
|
74
|
+
cost: CostBreakdown;
|
|
75
|
+
duration_ms: number;
|
|
76
|
+
success: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface CostBreakdown {
|
|
80
|
+
input_cost: number;
|
|
81
|
+
output_cost: number;
|
|
82
|
+
cache_cost: number;
|
|
83
|
+
total_cost: number;
|
|
84
|
+
currency: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ModelPricing {
|
|
88
|
+
model: string;
|
|
89
|
+
input_per_million: number;
|
|
90
|
+
output_per_million: number;
|
|
91
|
+
cache_creation_per_million?: number;
|
|
92
|
+
cache_read_per_million?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class TokenTracker {
|
|
96
|
+
private records: UsageRecord[] = [];
|
|
97
|
+
private pricing: Map<string, ModelPricing>;
|
|
98
|
+
private config: TokenTrackerConfig;
|
|
99
|
+
|
|
100
|
+
constructor(config: TokenTrackerConfig) {
|
|
101
|
+
this.config = config;
|
|
102
|
+
this.pricing = this.initializePricing();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Record usage from API response
|
|
106
|
+
record(
|
|
107
|
+
model: string,
|
|
108
|
+
usage: TokenUsage,
|
|
109
|
+
metadata?: UsageMetadata
|
|
110
|
+
): UsageRecord {
|
|
111
|
+
const pricing = this.pricing.get(model);
|
|
112
|
+
if (!pricing) {
|
|
113
|
+
throw new Error(`Unknown model: ${model}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cost = this.calculateCost(usage, pricing);
|
|
117
|
+
|
|
118
|
+
const record: UsageRecord = {
|
|
119
|
+
id: this.generateId(),
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
model,
|
|
122
|
+
agent: metadata?.agent,
|
|
123
|
+
task: metadata?.task,
|
|
124
|
+
flow: metadata?.flow,
|
|
125
|
+
usage,
|
|
126
|
+
cost,
|
|
127
|
+
duration_ms: metadata?.duration_ms || 0,
|
|
128
|
+
success: metadata?.success ?? true
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.records.push(record);
|
|
132
|
+
|
|
133
|
+
// Emit event for real-time tracking
|
|
134
|
+
EventBus.publish('cost.usage.recorded', record);
|
|
135
|
+
|
|
136
|
+
// Check budget alerts
|
|
137
|
+
BudgetManager.checkAlerts(record);
|
|
138
|
+
|
|
139
|
+
return record;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Get usage summary
|
|
143
|
+
getSummary(options?: SummaryOptions): UsageSummary {
|
|
144
|
+
const filtered = this.filterRecords(options);
|
|
145
|
+
|
|
146
|
+
const summary: UsageSummary = {
|
|
147
|
+
total_requests: filtered.length,
|
|
148
|
+
total_tokens: {
|
|
149
|
+
input: 0,
|
|
150
|
+
output: 0,
|
|
151
|
+
cache_creation: 0,
|
|
152
|
+
cache_read: 0,
|
|
153
|
+
total: 0
|
|
154
|
+
},
|
|
155
|
+
total_cost: {
|
|
156
|
+
input: 0,
|
|
157
|
+
output: 0,
|
|
158
|
+
cache: 0,
|
|
159
|
+
total: 0,
|
|
160
|
+
currency: 'USD'
|
|
161
|
+
},
|
|
162
|
+
by_model: {},
|
|
163
|
+
by_agent: {},
|
|
164
|
+
by_task: {},
|
|
165
|
+
period: {
|
|
166
|
+
from: options?.from || this.records[0]?.timestamp,
|
|
167
|
+
to: options?.to || new Date().toISOString()
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (const record of filtered) {
|
|
172
|
+
// Total tokens
|
|
173
|
+
summary.total_tokens.input += record.usage.input_tokens;
|
|
174
|
+
summary.total_tokens.output += record.usage.output_tokens;
|
|
175
|
+
summary.total_tokens.cache_creation += record.usage.cache_creation_tokens || 0;
|
|
176
|
+
summary.total_tokens.cache_read += record.usage.cache_read_tokens || 0;
|
|
177
|
+
summary.total_tokens.total += record.usage.total_tokens;
|
|
178
|
+
|
|
179
|
+
// Total cost
|
|
180
|
+
summary.total_cost.input += record.cost.input_cost;
|
|
181
|
+
summary.total_cost.output += record.cost.output_cost;
|
|
182
|
+
summary.total_cost.cache += record.cost.cache_cost;
|
|
183
|
+
summary.total_cost.total += record.cost.total_cost;
|
|
184
|
+
|
|
185
|
+
// By model
|
|
186
|
+
if (!summary.by_model[record.model]) {
|
|
187
|
+
summary.by_model[record.model] = { requests: 0, tokens: 0, cost: 0 };
|
|
188
|
+
}
|
|
189
|
+
summary.by_model[record.model].requests++;
|
|
190
|
+
summary.by_model[record.model].tokens += record.usage.total_tokens;
|
|
191
|
+
summary.by_model[record.model].cost += record.cost.total_cost;
|
|
192
|
+
|
|
193
|
+
// By agent
|
|
194
|
+
if (record.agent) {
|
|
195
|
+
if (!summary.by_agent[record.agent]) {
|
|
196
|
+
summary.by_agent[record.agent] = { requests: 0, tokens: 0, cost: 0 };
|
|
197
|
+
}
|
|
198
|
+
summary.by_agent[record.agent].requests++;
|
|
199
|
+
summary.by_agent[record.agent].tokens += record.usage.total_tokens;
|
|
200
|
+
summary.by_agent[record.agent].cost += record.cost.total_cost;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// By task
|
|
204
|
+
if (record.task) {
|
|
205
|
+
if (!summary.by_task[record.task]) {
|
|
206
|
+
summary.by_task[record.task] = { requests: 0, tokens: 0, cost: 0 };
|
|
207
|
+
}
|
|
208
|
+
summary.by_task[record.task].requests++;
|
|
209
|
+
summary.by_task[record.task].tokens += record.usage.total_tokens;
|
|
210
|
+
summary.by_task[record.task].cost += record.cost.total_cost;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return summary;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Get usage by time period
|
|
218
|
+
getTimeSeries(
|
|
219
|
+
granularity: 'minute' | 'hour' | 'day' | 'week' | 'month',
|
|
220
|
+
options?: TimeSeriesOptions
|
|
221
|
+
): TimeSeriesData[] {
|
|
222
|
+
const filtered = this.filterRecords(options);
|
|
223
|
+
const buckets = new Map<string, TimeSeriesData>();
|
|
224
|
+
|
|
225
|
+
for (const record of filtered) {
|
|
226
|
+
const bucket = this.getBucket(record.timestamp, granularity);
|
|
227
|
+
|
|
228
|
+
if (!buckets.has(bucket)) {
|
|
229
|
+
buckets.set(bucket, {
|
|
230
|
+
timestamp: bucket,
|
|
231
|
+
requests: 0,
|
|
232
|
+
tokens: 0,
|
|
233
|
+
cost: 0
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const data = buckets.get(bucket)!;
|
|
238
|
+
data.requests++;
|
|
239
|
+
data.tokens += record.usage.total_tokens;
|
|
240
|
+
data.cost += record.cost.total_cost;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return Array.from(buckets.values()).sort((a, b) =>
|
|
244
|
+
a.timestamp.localeCompare(b.timestamp)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Estimate cost for task
|
|
249
|
+
estimate(
|
|
250
|
+
model: string,
|
|
251
|
+
estimatedInputTokens: number,
|
|
252
|
+
estimatedOutputTokens: number
|
|
253
|
+
): CostBreakdown {
|
|
254
|
+
const pricing = this.pricing.get(model);
|
|
255
|
+
if (!pricing) {
|
|
256
|
+
throw new Error(`Unknown model: ${model}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return this.calculateCost(
|
|
260
|
+
{
|
|
261
|
+
input_tokens: estimatedInputTokens,
|
|
262
|
+
output_tokens: estimatedOutputTokens,
|
|
263
|
+
total_tokens: estimatedInputTokens + estimatedOutputTokens
|
|
264
|
+
},
|
|
265
|
+
pricing
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Export data
|
|
270
|
+
export(format: 'json' | 'csv'): string {
|
|
271
|
+
if (format === 'csv') {
|
|
272
|
+
return this.exportCSV();
|
|
273
|
+
}
|
|
274
|
+
return JSON.stringify(this.records, null, 2);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Clear old records
|
|
278
|
+
cleanup(olderThan: Date): number {
|
|
279
|
+
const cutoff = olderThan.toISOString();
|
|
280
|
+
const before = this.records.length;
|
|
281
|
+
this.records = this.records.filter(r => r.timestamp >= cutoff);
|
|
282
|
+
return before - this.records.length;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private calculateCost(usage: TokenUsage, pricing: ModelPricing): CostBreakdown {
|
|
286
|
+
const input_cost = (usage.input_tokens / 1_000_000) * pricing.input_per_million;
|
|
287
|
+
const output_cost = (usage.output_tokens / 1_000_000) * pricing.output_per_million;
|
|
288
|
+
|
|
289
|
+
let cache_cost = 0;
|
|
290
|
+
if (usage.cache_creation_tokens && pricing.cache_creation_per_million) {
|
|
291
|
+
cache_cost += (usage.cache_creation_tokens / 1_000_000) * pricing.cache_creation_per_million;
|
|
292
|
+
}
|
|
293
|
+
if (usage.cache_read_tokens && pricing.cache_read_per_million) {
|
|
294
|
+
cache_cost += (usage.cache_read_tokens / 1_000_000) * pricing.cache_read_per_million;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
input_cost,
|
|
299
|
+
output_cost,
|
|
300
|
+
cache_cost,
|
|
301
|
+
total_cost: input_cost + output_cost + cache_cost,
|
|
302
|
+
currency: 'USD'
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private initializePricing(): Map<string, ModelPricing> {
|
|
307
|
+
return new Map([
|
|
308
|
+
['claude-3-5-haiku-20241022', {
|
|
309
|
+
model: 'claude-3-5-haiku-20241022',
|
|
310
|
+
input_per_million: 0.80,
|
|
311
|
+
output_per_million: 4.00,
|
|
312
|
+
cache_creation_per_million: 1.00,
|
|
313
|
+
cache_read_per_million: 0.08
|
|
314
|
+
}],
|
|
315
|
+
['claude-sonnet-4-20250514', {
|
|
316
|
+
model: 'claude-sonnet-4-20250514',
|
|
317
|
+
input_per_million: 3.00,
|
|
318
|
+
output_per_million: 15.00,
|
|
319
|
+
cache_creation_per_million: 3.75,
|
|
320
|
+
cache_read_per_million: 0.30
|
|
321
|
+
}],
|
|
322
|
+
['claude-opus-4-5-20251101', {
|
|
323
|
+
model: 'claude-opus-4-5-20251101',
|
|
324
|
+
input_per_million: 15.00,
|
|
325
|
+
output_per_million: 75.00,
|
|
326
|
+
cache_creation_per_million: 18.75,
|
|
327
|
+
cache_read_per_million: 1.50
|
|
328
|
+
}]
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private filterRecords(options?: SummaryOptions): UsageRecord[] {
|
|
333
|
+
let filtered = this.records;
|
|
334
|
+
|
|
335
|
+
if (options?.from) {
|
|
336
|
+
filtered = filtered.filter(r => r.timestamp >= options.from!);
|
|
337
|
+
}
|
|
338
|
+
if (options?.to) {
|
|
339
|
+
filtered = filtered.filter(r => r.timestamp <= options.to!);
|
|
340
|
+
}
|
|
341
|
+
if (options?.model) {
|
|
342
|
+
filtered = filtered.filter(r => r.model === options.model);
|
|
343
|
+
}
|
|
344
|
+
if (options?.agent) {
|
|
345
|
+
filtered = filtered.filter(r => r.agent === options.agent);
|
|
346
|
+
}
|
|
347
|
+
if (options?.task) {
|
|
348
|
+
filtered = filtered.filter(r => r.task === options.task);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return filtered;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private getBucket(timestamp: string, granularity: string): string {
|
|
355
|
+
const date = new Date(timestamp);
|
|
356
|
+
|
|
357
|
+
switch (granularity) {
|
|
358
|
+
case 'minute':
|
|
359
|
+
return date.toISOString().slice(0, 16);
|
|
360
|
+
case 'hour':
|
|
361
|
+
return date.toISOString().slice(0, 13);
|
|
362
|
+
case 'day':
|
|
363
|
+
return date.toISOString().slice(0, 10);
|
|
364
|
+
case 'week':
|
|
365
|
+
const week = this.getWeekNumber(date);
|
|
366
|
+
return `${date.getFullYear()}-W${week.toString().padStart(2, '0')}`;
|
|
367
|
+
case 'month':
|
|
368
|
+
return date.toISOString().slice(0, 7);
|
|
369
|
+
default:
|
|
370
|
+
return date.toISOString().slice(0, 10);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private getWeekNumber(date: Date): number {
|
|
375
|
+
const firstDay = new Date(date.getFullYear(), 0, 1);
|
|
376
|
+
const days = Math.floor((date.getTime() - firstDay.getTime()) / 86400000);
|
|
377
|
+
return Math.ceil((days + firstDay.getDay() + 1) / 7);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private exportCSV(): string {
|
|
381
|
+
const headers = [
|
|
382
|
+
'id', 'timestamp', 'model', 'agent', 'task',
|
|
383
|
+
'input_tokens', 'output_tokens', 'total_tokens',
|
|
384
|
+
'input_cost', 'output_cost', 'total_cost', 'duration_ms'
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
const rows = this.records.map(r => [
|
|
388
|
+
r.id, r.timestamp, r.model, r.agent || '', r.task || '',
|
|
389
|
+
r.usage.input_tokens, r.usage.output_tokens, r.usage.total_tokens,
|
|
390
|
+
r.cost.input_cost.toFixed(6), r.cost.output_cost.toFixed(6),
|
|
391
|
+
r.cost.total_cost.toFixed(6), r.duration_ms
|
|
392
|
+
]);
|
|
393
|
+
|
|
394
|
+
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private generateId(): string {
|
|
398
|
+
return `usage_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## BudgetManager
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
interface Budget {
|
|
409
|
+
id: string;
|
|
410
|
+
name: string;
|
|
411
|
+
type: 'session' | 'daily' | 'weekly' | 'monthly' | 'total';
|
|
412
|
+
limit: number;
|
|
413
|
+
currency: string;
|
|
414
|
+
spent: number;
|
|
415
|
+
remaining: number;
|
|
416
|
+
alerts: BudgetAlert[];
|
|
417
|
+
action_on_exceed: 'warn' | 'pause' | 'stop' | 'downgrade';
|
|
418
|
+
created_at: string;
|
|
419
|
+
reset_at?: string;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
interface BudgetAlert {
|
|
423
|
+
type: 'warning' | 'critical' | 'exceeded';
|
|
424
|
+
threshold: number; // Percentage (0-100)
|
|
425
|
+
triggered: boolean;
|
|
426
|
+
triggered_at?: string;
|
|
427
|
+
notified: boolean;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
class BudgetManager {
|
|
431
|
+
private budgets: Map<string, Budget>;
|
|
432
|
+
private config: BudgetManagerConfig;
|
|
433
|
+
|
|
434
|
+
constructor(config: BudgetManagerConfig) {
|
|
435
|
+
this.config = config;
|
|
436
|
+
this.budgets = new Map();
|
|
437
|
+
this.initializeDefaultBudgets();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Create budget
|
|
441
|
+
createBudget(options: CreateBudgetOptions): Budget {
|
|
442
|
+
const budget: Budget = {
|
|
443
|
+
id: this.generateId(),
|
|
444
|
+
name: options.name,
|
|
445
|
+
type: options.type,
|
|
446
|
+
limit: options.limit,
|
|
447
|
+
currency: options.currency || 'USD',
|
|
448
|
+
spent: 0,
|
|
449
|
+
remaining: options.limit,
|
|
450
|
+
alerts: [
|
|
451
|
+
{ type: 'warning', threshold: 80, triggered: false, notified: false },
|
|
452
|
+
{ type: 'critical', threshold: 95, triggered: false, notified: false },
|
|
453
|
+
{ type: 'exceeded', threshold: 100, triggered: false, notified: false }
|
|
454
|
+
],
|
|
455
|
+
action_on_exceed: options.action_on_exceed || 'warn',
|
|
456
|
+
created_at: new Date().toISOString(),
|
|
457
|
+
reset_at: this.calculateResetTime(options.type)
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
this.budgets.set(budget.id, budget);
|
|
461
|
+
return budget;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Update budget with cost
|
|
465
|
+
recordSpend(budgetId: string, cost: number): Budget {
|
|
466
|
+
const budget = this.budgets.get(budgetId);
|
|
467
|
+
if (!budget) {
|
|
468
|
+
throw new Error(`Budget not found: ${budgetId}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
budget.spent += cost;
|
|
472
|
+
budget.remaining = Math.max(0, budget.limit - budget.spent);
|
|
473
|
+
|
|
474
|
+
this.checkAlerts(budget);
|
|
475
|
+
|
|
476
|
+
return budget;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check all budgets against usage record
|
|
480
|
+
checkAlerts(record?: UsageRecord): void {
|
|
481
|
+
for (const [_, budget] of this.budgets) {
|
|
482
|
+
if (record) {
|
|
483
|
+
budget.spent += record.cost.total_cost;
|
|
484
|
+
budget.remaining = Math.max(0, budget.limit - budget.spent);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const percentage = (budget.spent / budget.limit) * 100;
|
|
488
|
+
|
|
489
|
+
for (const alert of budget.alerts) {
|
|
490
|
+
if (percentage >= alert.threshold && !alert.triggered) {
|
|
491
|
+
alert.triggered = true;
|
|
492
|
+
alert.triggered_at = new Date().toISOString();
|
|
493
|
+
|
|
494
|
+
// Emit alert event
|
|
495
|
+
EventBus.publish('cost.budget.alert', {
|
|
496
|
+
budget_id: budget.id,
|
|
497
|
+
budget_name: budget.name,
|
|
498
|
+
alert_type: alert.type,
|
|
499
|
+
threshold: alert.threshold,
|
|
500
|
+
current_percentage: percentage,
|
|
501
|
+
spent: budget.spent,
|
|
502
|
+
limit: budget.limit
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Take action if exceeded
|
|
506
|
+
if (alert.type === 'exceeded') {
|
|
507
|
+
this.handleExceeded(budget);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Get budget status
|
|
515
|
+
getStatus(budgetId?: string): BudgetStatus | BudgetStatus[] {
|
|
516
|
+
if (budgetId) {
|
|
517
|
+
const budget = this.budgets.get(budgetId);
|
|
518
|
+
if (!budget) throw new Error(`Budget not found: ${budgetId}`);
|
|
519
|
+
return this.formatStatus(budget);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return Array.from(this.budgets.values()).map(b => this.formatStatus(b));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Reset budget
|
|
526
|
+
resetBudget(budgetId: string): Budget {
|
|
527
|
+
const budget = this.budgets.get(budgetId);
|
|
528
|
+
if (!budget) {
|
|
529
|
+
throw new Error(`Budget not found: ${budgetId}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
budget.spent = 0;
|
|
533
|
+
budget.remaining = budget.limit;
|
|
534
|
+
budget.alerts.forEach(a => {
|
|
535
|
+
a.triggered = false;
|
|
536
|
+
a.triggered_at = undefined;
|
|
537
|
+
a.notified = false;
|
|
538
|
+
});
|
|
539
|
+
budget.reset_at = this.calculateResetTime(budget.type);
|
|
540
|
+
|
|
541
|
+
return budget;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Check if operation is allowed
|
|
545
|
+
canSpend(estimatedCost: number): BudgetDecision {
|
|
546
|
+
const decisions: BudgetDecision[] = [];
|
|
547
|
+
|
|
548
|
+
for (const [_, budget] of this.budgets) {
|
|
549
|
+
const projectedSpent = budget.spent + estimatedCost;
|
|
550
|
+
const projectedPercentage = (projectedSpent / budget.limit) * 100;
|
|
551
|
+
|
|
552
|
+
if (projectedPercentage >= 100) {
|
|
553
|
+
decisions.push({
|
|
554
|
+
allowed: budget.action_on_exceed === 'warn',
|
|
555
|
+
budget_id: budget.id,
|
|
556
|
+
budget_name: budget.name,
|
|
557
|
+
reason: `Would exceed ${budget.name} budget`,
|
|
558
|
+
projected_percentage: projectedPercentage,
|
|
559
|
+
recommended_action: budget.action_on_exceed
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Return most restrictive decision
|
|
565
|
+
const blocked = decisions.find(d => !d.allowed);
|
|
566
|
+
if (blocked) return blocked;
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
allowed: true,
|
|
570
|
+
reason: 'Within all budgets'
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Auto-reset periodic budgets
|
|
575
|
+
autoReset(): void {
|
|
576
|
+
const now = new Date();
|
|
577
|
+
|
|
578
|
+
for (const [_, budget] of this.budgets) {
|
|
579
|
+
if (budget.reset_at && new Date(budget.reset_at) <= now) {
|
|
580
|
+
this.resetBudget(budget.id);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private handleExceeded(budget: Budget): void {
|
|
586
|
+
switch (budget.action_on_exceed) {
|
|
587
|
+
case 'pause':
|
|
588
|
+
// Trigger interrupt
|
|
589
|
+
InterruptManager.trigger({
|
|
590
|
+
type: 'confirmation',
|
|
591
|
+
title: 'Budget Exceeded',
|
|
592
|
+
message: `${budget.name} budget has been exceeded. Continue?`,
|
|
593
|
+
options: [
|
|
594
|
+
{ id: 'continue', label: 'Continue Anyway' },
|
|
595
|
+
{ id: 'stop', label: 'Stop Execution' }
|
|
596
|
+
]
|
|
597
|
+
});
|
|
598
|
+
break;
|
|
599
|
+
|
|
600
|
+
case 'stop':
|
|
601
|
+
// Stop all flows
|
|
602
|
+
FlowEngine.pauseAll('Budget exceeded');
|
|
603
|
+
break;
|
|
604
|
+
|
|
605
|
+
case 'downgrade':
|
|
606
|
+
// Switch to cheaper model
|
|
607
|
+
ModelOptimizer.forceDowngrade();
|
|
608
|
+
break;
|
|
609
|
+
|
|
610
|
+
case 'warn':
|
|
611
|
+
default:
|
|
612
|
+
// Just log warning
|
|
613
|
+
console.warn(`Budget exceeded: ${budget.name}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private initializeDefaultBudgets(): void {
|
|
618
|
+
if (this.config.defaultBudgets?.session) {
|
|
619
|
+
this.createBudget({
|
|
620
|
+
name: 'Session Budget',
|
|
621
|
+
type: 'session',
|
|
622
|
+
limit: this.config.defaultBudgets.session,
|
|
623
|
+
action_on_exceed: 'warn'
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (this.config.defaultBudgets?.daily) {
|
|
628
|
+
this.createBudget({
|
|
629
|
+
name: 'Daily Budget',
|
|
630
|
+
type: 'daily',
|
|
631
|
+
limit: this.config.defaultBudgets.daily,
|
|
632
|
+
action_on_exceed: 'pause'
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (this.config.defaultBudgets?.monthly) {
|
|
637
|
+
this.createBudget({
|
|
638
|
+
name: 'Monthly Budget',
|
|
639
|
+
type: 'monthly',
|
|
640
|
+
limit: this.config.defaultBudgets.monthly,
|
|
641
|
+
action_on_exceed: 'stop'
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private calculateResetTime(type: string): string | undefined {
|
|
647
|
+
const now = new Date();
|
|
648
|
+
|
|
649
|
+
switch (type) {
|
|
650
|
+
case 'daily':
|
|
651
|
+
return new Date(now.setDate(now.getDate() + 1)).toISOString().slice(0, 10) + 'T00:00:00Z';
|
|
652
|
+
case 'weekly':
|
|
653
|
+
return new Date(now.setDate(now.getDate() + 7)).toISOString().slice(0, 10) + 'T00:00:00Z';
|
|
654
|
+
case 'monthly':
|
|
655
|
+
return new Date(now.setMonth(now.getMonth() + 1)).toISOString().slice(0, 7) + '-01T00:00:00Z';
|
|
656
|
+
default:
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private formatStatus(budget: Budget): BudgetStatus {
|
|
662
|
+
const percentage = (budget.spent / budget.limit) * 100;
|
|
663
|
+
return {
|
|
664
|
+
id: budget.id,
|
|
665
|
+
name: budget.name,
|
|
666
|
+
type: budget.type,
|
|
667
|
+
spent: budget.spent,
|
|
668
|
+
limit: budget.limit,
|
|
669
|
+
remaining: budget.remaining,
|
|
670
|
+
percentage,
|
|
671
|
+
status: percentage >= 100 ? 'exceeded' :
|
|
672
|
+
percentage >= 95 ? 'critical' :
|
|
673
|
+
percentage >= 80 ? 'warning' : 'ok',
|
|
674
|
+
resets_at: budget.reset_at
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private generateId(): string {
|
|
679
|
+
return `budget_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## ModelOptimizer
|
|
687
|
+
|
|
688
|
+
```typescript
|
|
689
|
+
interface ModelProfile {
|
|
690
|
+
model: string;
|
|
691
|
+
tier: 'economy' | 'standard' | 'premium';
|
|
692
|
+
strengths: string[];
|
|
693
|
+
weaknesses: string[];
|
|
694
|
+
cost_per_1k_tokens: number;
|
|
695
|
+
quality_score: number; // 0-100
|
|
696
|
+
speed_score: number; // 0-100
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
interface TaskAnalysis {
|
|
700
|
+
complexity: 'simple' | 'medium' | 'complex';
|
|
701
|
+
requires_reasoning: boolean;
|
|
702
|
+
requires_creativity: boolean;
|
|
703
|
+
requires_accuracy: boolean;
|
|
704
|
+
estimated_tokens: number;
|
|
705
|
+
priority: 'speed' | 'quality' | 'cost';
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
interface ModelRecommendation {
|
|
709
|
+
recommended_model: string;
|
|
710
|
+
tier: string;
|
|
711
|
+
confidence: number;
|
|
712
|
+
estimated_cost: number;
|
|
713
|
+
alternatives: Array<{
|
|
714
|
+
model: string;
|
|
715
|
+
cost: number;
|
|
716
|
+
trade_off: string;
|
|
717
|
+
}>;
|
|
718
|
+
reasoning: string;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
class ModelOptimizer {
|
|
722
|
+
private profiles: Map<string, ModelProfile>;
|
|
723
|
+
private history: Map<string, TaskResult[]>;
|
|
724
|
+
private config: ModelOptimizerConfig;
|
|
725
|
+
|
|
726
|
+
constructor(config: ModelOptimizerConfig) {
|
|
727
|
+
this.config = config;
|
|
728
|
+
this.profiles = this.initializeProfiles();
|
|
729
|
+
this.history = new Map();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Analyze task and recommend model
|
|
733
|
+
recommend(task: TaskAnalysis, budget?: number): ModelRecommendation {
|
|
734
|
+
const candidates = this.rankCandidates(task);
|
|
735
|
+
|
|
736
|
+
// Filter by budget if provided
|
|
737
|
+
const affordable = budget
|
|
738
|
+
? candidates.filter(c => c.estimated_cost <= budget)
|
|
739
|
+
: candidates;
|
|
740
|
+
|
|
741
|
+
if (affordable.length === 0) {
|
|
742
|
+
// Force cheapest option
|
|
743
|
+
const cheapest = candidates.sort((a, b) => a.estimated_cost - b.estimated_cost)[0];
|
|
744
|
+
return {
|
|
745
|
+
recommended_model: cheapest.model,
|
|
746
|
+
tier: cheapest.tier,
|
|
747
|
+
confidence: 0.5,
|
|
748
|
+
estimated_cost: cheapest.estimated_cost,
|
|
749
|
+
alternatives: [],
|
|
750
|
+
reasoning: 'Budget constraint - using cheapest available model'
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const best = affordable[0];
|
|
755
|
+
const alternatives = affordable.slice(1, 4).map(c => ({
|
|
756
|
+
model: c.model,
|
|
757
|
+
cost: c.estimated_cost,
|
|
758
|
+
trade_off: this.getTradeOff(best, c)
|
|
759
|
+
}));
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
recommended_model: best.model,
|
|
763
|
+
tier: best.tier,
|
|
764
|
+
confidence: best.score / 100,
|
|
765
|
+
estimated_cost: best.estimated_cost,
|
|
766
|
+
alternatives,
|
|
767
|
+
reasoning: this.generateReasoning(task, best)
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Auto-select model for task type
|
|
772
|
+
autoSelect(taskType: string, context?: AutoSelectContext): string {
|
|
773
|
+
const mappings: Record<string, string> = {
|
|
774
|
+
// Exploration tasks → Haiku
|
|
775
|
+
'explore': 'claude-3-5-haiku-20241022',
|
|
776
|
+
'search': 'claude-3-5-haiku-20241022',
|
|
777
|
+
'list': 'claude-3-5-haiku-20241022',
|
|
778
|
+
'quick-fix': 'claude-3-5-haiku-20241022',
|
|
779
|
+
|
|
780
|
+
// Standard tasks → Sonnet
|
|
781
|
+
'implement': 'claude-sonnet-4-20250514',
|
|
782
|
+
'refactor': 'claude-sonnet-4-20250514',
|
|
783
|
+
'test': 'claude-sonnet-4-20250514',
|
|
784
|
+
'document': 'claude-sonnet-4-20250514',
|
|
785
|
+
|
|
786
|
+
// Complex tasks → Opus
|
|
787
|
+
'architect': 'claude-opus-4-5-20251101',
|
|
788
|
+
'review': 'claude-opus-4-5-20251101',
|
|
789
|
+
'debug-complex': 'claude-opus-4-5-20251101',
|
|
790
|
+
'security-audit': 'claude-opus-4-5-20251101'
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
let model = mappings[taskType] || 'claude-sonnet-4-20250514';
|
|
794
|
+
|
|
795
|
+
// Check budget constraints
|
|
796
|
+
if (context?.budget) {
|
|
797
|
+
const decision = BudgetManager.canSpend(this.estimateCost(model, context.estimatedTokens || 1000));
|
|
798
|
+
if (!decision.allowed && decision.recommended_action === 'downgrade') {
|
|
799
|
+
model = this.downgrade(model);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Check mode override
|
|
804
|
+
if (context?.mode === 'economy') {
|
|
805
|
+
model = 'claude-3-5-haiku-20241022';
|
|
806
|
+
} else if (context?.mode === 'quality') {
|
|
807
|
+
model = 'claude-opus-4-5-20251101';
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return model;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Force downgrade to cheaper model
|
|
814
|
+
forceDowngrade(): void {
|
|
815
|
+
this.config.forceEconomy = true;
|
|
816
|
+
EventBus.publish('cost.model.downgraded', {
|
|
817
|
+
reason: 'budget_exceeded',
|
|
818
|
+
new_default: 'claude-3-5-haiku-20241022'
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Get cost savings report
|
|
823
|
+
getSavingsReport(options?: ReportOptions): SavingsReport {
|
|
824
|
+
// Analyze historical usage
|
|
825
|
+
const summary = TokenTracker.getSummary(options);
|
|
826
|
+
|
|
827
|
+
// Calculate potential savings with different strategies
|
|
828
|
+
const strategies = [
|
|
829
|
+
this.calculateSavings(summary, 'aggressive'), // All Haiku
|
|
830
|
+
this.calculateSavings(summary, 'balanced'), // Smart routing
|
|
831
|
+
this.calculateSavings(summary, 'quality') // Minimal changes
|
|
832
|
+
];
|
|
833
|
+
|
|
834
|
+
return {
|
|
835
|
+
current_cost: summary.total_cost.total,
|
|
836
|
+
strategies,
|
|
837
|
+
recommendations: this.generateSavingsRecommendations(summary)
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Learn from task results
|
|
842
|
+
recordResult(model: string, task: string, result: TaskResult): void {
|
|
843
|
+
if (!this.history.has(model)) {
|
|
844
|
+
this.history.set(model, []);
|
|
845
|
+
}
|
|
846
|
+
this.history.get(model)!.push(result);
|
|
847
|
+
|
|
848
|
+
// Update model profile based on results
|
|
849
|
+
this.updateProfile(model, result);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private rankCandidates(task: TaskAnalysis): RankedCandidate[] {
|
|
853
|
+
const candidates: RankedCandidate[] = [];
|
|
854
|
+
|
|
855
|
+
for (const [_, profile] of this.profiles) {
|
|
856
|
+
const score = this.calculateScore(task, profile);
|
|
857
|
+
const estimated_cost = this.estimateCost(profile.model, task.estimated_tokens);
|
|
858
|
+
|
|
859
|
+
candidates.push({
|
|
860
|
+
model: profile.model,
|
|
861
|
+
tier: profile.tier,
|
|
862
|
+
score,
|
|
863
|
+
estimated_cost
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Sort by score (descending)
|
|
868
|
+
return candidates.sort((a, b) => b.score - a.score);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private calculateScore(task: TaskAnalysis, profile: ModelProfile): number {
|
|
872
|
+
let score = 50; // Base score
|
|
873
|
+
|
|
874
|
+
// Complexity matching
|
|
875
|
+
if (task.complexity === 'simple' && profile.tier === 'economy') score += 20;
|
|
876
|
+
if (task.complexity === 'medium' && profile.tier === 'standard') score += 20;
|
|
877
|
+
if (task.complexity === 'complex' && profile.tier === 'premium') score += 20;
|
|
878
|
+
|
|
879
|
+
// Priority weighting
|
|
880
|
+
switch (task.priority) {
|
|
881
|
+
case 'speed':
|
|
882
|
+
score += profile.speed_score * 0.3;
|
|
883
|
+
break;
|
|
884
|
+
case 'quality':
|
|
885
|
+
score += profile.quality_score * 0.3;
|
|
886
|
+
break;
|
|
887
|
+
case 'cost':
|
|
888
|
+
score += (100 - profile.cost_per_1k_tokens * 10) * 0.3;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Requirement bonuses
|
|
893
|
+
if (task.requires_reasoning && profile.strengths.includes('reasoning')) score += 10;
|
|
894
|
+
if (task.requires_creativity && profile.strengths.includes('creativity')) score += 10;
|
|
895
|
+
if (task.requires_accuracy && profile.strengths.includes('accuracy')) score += 10;
|
|
896
|
+
|
|
897
|
+
return Math.min(100, score);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
private estimateCost(model: string, tokens: number): number {
|
|
901
|
+
const profile = this.profiles.get(model);
|
|
902
|
+
if (!profile) return 0;
|
|
903
|
+
return (tokens / 1000) * profile.cost_per_1k_tokens;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private downgrade(model: string): string {
|
|
907
|
+
const downgrades: Record<string, string> = {
|
|
908
|
+
'claude-opus-4-5-20251101': 'claude-sonnet-4-20250514',
|
|
909
|
+
'claude-sonnet-4-20250514': 'claude-3-5-haiku-20241022',
|
|
910
|
+
'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022'
|
|
911
|
+
};
|
|
912
|
+
return downgrades[model] || model;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private initializeProfiles(): Map<string, ModelProfile> {
|
|
916
|
+
return new Map([
|
|
917
|
+
['claude-3-5-haiku-20241022', {
|
|
918
|
+
model: 'claude-3-5-haiku-20241022',
|
|
919
|
+
tier: 'economy',
|
|
920
|
+
strengths: ['speed', 'efficiency', 'simple-tasks'],
|
|
921
|
+
weaknesses: ['complex-reasoning', 'nuance'],
|
|
922
|
+
cost_per_1k_tokens: 0.0024,
|
|
923
|
+
quality_score: 70,
|
|
924
|
+
speed_score: 95
|
|
925
|
+
}],
|
|
926
|
+
['claude-sonnet-4-20250514', {
|
|
927
|
+
model: 'claude-sonnet-4-20250514',
|
|
928
|
+
tier: 'standard',
|
|
929
|
+
strengths: ['balance', 'coding', 'reasoning'],
|
|
930
|
+
weaknesses: ['highest-complexity'],
|
|
931
|
+
cost_per_1k_tokens: 0.009,
|
|
932
|
+
quality_score: 85,
|
|
933
|
+
speed_score: 80
|
|
934
|
+
}],
|
|
935
|
+
['claude-opus-4-5-20251101', {
|
|
936
|
+
model: 'claude-opus-4-5-20251101',
|
|
937
|
+
tier: 'premium',
|
|
938
|
+
strengths: ['reasoning', 'creativity', 'accuracy', 'complex-tasks'],
|
|
939
|
+
weaknesses: ['cost', 'speed'],
|
|
940
|
+
cost_per_1k_tokens: 0.045,
|
|
941
|
+
quality_score: 98,
|
|
942
|
+
speed_score: 60
|
|
943
|
+
}]
|
|
944
|
+
]);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private getTradeOff(best: RankedCandidate, alt: RankedCandidate): string {
|
|
948
|
+
const costDiff = ((alt.estimated_cost - best.estimated_cost) / best.estimated_cost * 100).toFixed(0);
|
|
949
|
+
if (alt.tier === 'economy') return `${Math.abs(parseInt(costDiff))}% cheaper, less capable`;
|
|
950
|
+
if (alt.tier === 'premium') return `${costDiff}% more expensive, higher quality`;
|
|
951
|
+
return 'Similar capability, different cost';
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private generateReasoning(task: TaskAnalysis, candidate: RankedCandidate): string {
|
|
955
|
+
return `Selected ${candidate.model} (${candidate.tier}) for ${task.complexity} task. ` +
|
|
956
|
+
`Priority: ${task.priority}. Estimated cost: $${candidate.estimated_cost.toFixed(4)}`;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
private calculateSavings(summary: UsageSummary, strategy: string): SavingsStrategy {
|
|
960
|
+
// Implementation of savings calculation
|
|
961
|
+
return {
|
|
962
|
+
strategy,
|
|
963
|
+
potential_savings: 0,
|
|
964
|
+
percentage_saved: 0,
|
|
965
|
+
trade_offs: []
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
private generateSavingsRecommendations(summary: UsageSummary): string[] {
|
|
970
|
+
const recommendations: string[] = [];
|
|
971
|
+
|
|
972
|
+
// Check for Opus overuse
|
|
973
|
+
const opusUsage = summary.by_model['claude-opus-4-5-20251101'];
|
|
974
|
+
if (opusUsage && opusUsage.requests > 10) {
|
|
975
|
+
recommendations.push('Consider using Sonnet for standard coding tasks to reduce costs by ~70%');
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Check for exploration with expensive models
|
|
979
|
+
const explorerUsage = summary.by_agent['Explore'];
|
|
980
|
+
if (explorerUsage && explorerUsage.cost > 1) {
|
|
981
|
+
recommendations.push('Switch exploration agents to Haiku for ~85% cost reduction');
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return recommendations;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private updateProfile(model: string, result: TaskResult): void {
|
|
988
|
+
// Dynamic profile updates based on actual performance
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
---
|
|
994
|
+
|
|
995
|
+
## Dashboard de Costos
|
|
996
|
+
|
|
997
|
+
```
|
|
998
|
+
╔═══════════════════════════════════════════════════════════════════════════╗
|
|
999
|
+
║ COST DASHBOARD ║
|
|
1000
|
+
╠═══════════════════════════════════════════════════════════════════════════╣
|
|
1001
|
+
║ ║
|
|
1002
|
+
║ SESSION SUMMARY BUDGET STATUS ║
|
|
1003
|
+
║ ─────────────── ───────────── ║
|
|
1004
|
+
║ Total Requests: 47 Session: ████████░░ 78% ($7.80/$10) ║
|
|
1005
|
+
║ Total Tokens: 125,432 Daily: ████░░░░░░ 42% ($21/$50) ║
|
|
1006
|
+
║ Total Cost: $7.82 Monthly: ██░░░░░░░░ 15% ($78/$500) ║
|
|
1007
|
+
║ ║
|
|
1008
|
+
║ COST BY MODEL COST BY AGENT ║
|
|
1009
|
+
║ ───────────── ───────────── ║
|
|
1010
|
+
║ Haiku ████░░░░░░ $0.45 (6%) Explore ████░░░░░░ $1.20 ║
|
|
1011
|
+
║ Sonnet ██████░░░░ $2.87 (37%) Executor ██████████ $4.50 ║
|
|
1012
|
+
║ Opus ████████░░ $4.50 (57%) Reviewer ████░░░░░░ $1.12 ║
|
|
1013
|
+
║ QA ██░░░░░░░░ $1.00 ║
|
|
1014
|
+
║ ║
|
|
1015
|
+
║ RECENT ACTIVITY ║
|
|
1016
|
+
║ ─────────────── ║
|
|
1017
|
+
║ 12:45:32 Opus implement-feature $0.85 ✓ ║
|
|
1018
|
+
║ 12:44:15 Sonnet review-code $0.32 ✓ ║
|
|
1019
|
+
║ 12:43:01 Haiku explore-codebase $0.08 ✓ ║
|
|
1020
|
+
║ 12:42:30 Opus architect-solution $1.20 ✓ ║
|
|
1021
|
+
║ ║
|
|
1022
|
+
║ RECOMMENDATIONS ║
|
|
1023
|
+
║ ─────────────── ║
|
|
1024
|
+
║ ⚡ Switch exploration to Haiku: Save ~$0.90/session ║
|
|
1025
|
+
║ 💡 Use Sonnet for standard reviews: Save ~$0.45/review ║
|
|
1026
|
+
║ ║
|
|
1027
|
+
╚═══════════════════════════════════════════════════════════════════════════╝
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
---
|
|
1031
|
+
|
|
1032
|
+
## Configuración
|
|
1033
|
+
|
|
1034
|
+
```json
|
|
1035
|
+
{
|
|
1036
|
+
"costTracking": {
|
|
1037
|
+
"enabled": true,
|
|
1038
|
+
"tokenTracker": {
|
|
1039
|
+
"enabled": true,
|
|
1040
|
+
"persistence": "file",
|
|
1041
|
+
"retentionDays": 90
|
|
1042
|
+
},
|
|
1043
|
+
"budgets": {
|
|
1044
|
+
"session": {
|
|
1045
|
+
"limit": 10.00,
|
|
1046
|
+
"action_on_exceed": "warn"
|
|
1047
|
+
},
|
|
1048
|
+
"daily": {
|
|
1049
|
+
"limit": 50.00,
|
|
1050
|
+
"action_on_exceed": "pause"
|
|
1051
|
+
},
|
|
1052
|
+
"monthly": {
|
|
1053
|
+
"limit": 500.00,
|
|
1054
|
+
"action_on_exceed": "stop"
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
"optimizer": {
|
|
1058
|
+
"enabled": true,
|
|
1059
|
+
"autoSelect": true,
|
|
1060
|
+
"mode": "balanced"
|
|
1061
|
+
},
|
|
1062
|
+
"alerts": {
|
|
1063
|
+
"warning": 80,
|
|
1064
|
+
"critical": 95
|
|
1065
|
+
},
|
|
1066
|
+
"reporting": {
|
|
1067
|
+
"dailySummary": true,
|
|
1068
|
+
"weeklyReport": true
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
## Comandos
|
|
1077
|
+
|
|
1078
|
+
```bash
|
|
1079
|
+
/elsabro:cost # Ver dashboard de costos
|
|
1080
|
+
/elsabro:cost summary # Resumen de sesión
|
|
1081
|
+
/elsabro:cost budget # Estado de presupuestos
|
|
1082
|
+
/elsabro:cost export csv # Exportar datos
|
|
1083
|
+
/elsabro:cost optimize # Recomendaciones de ahorro
|
|
1084
|
+
/elsabro:cost set-budget daily 100 # Configurar presupuesto
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
---
|
|
1088
|
+
|
|
1089
|
+
## Changelog
|
|
1090
|
+
|
|
1091
|
+
- **v3.3.0**: Initial Cost Tracking System
|
|
1092
|
+
- TokenTracker with per-request metering
|
|
1093
|
+
- BudgetManager with alerts
|
|
1094
|
+
- ModelOptimizer for smart model selection
|
|
1095
|
+
- Cost dashboard ASCII
|