opencode-token-tracker 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/README.zh-CN.md +385 -0
- package/dist/bin/opencode-tokens.js +30 -94
- package/dist/index.js +136 -147
- package/dist/lib/shared.d.ts +48 -0
- package/dist/lib/shared.js +357 -0
- package/dist/test/shared.test.d.ts +1 -0
- package/dist/test/shared.test.js +378 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BUILTIN_PRICING, DEFAULT_CONFIG, findModelConfigPricing, formatCost, formatTokens, getStartOfDay, getStartOfWeek, getStartOfMonth, validateConfig } from "./lib/shared.js";
|
|
1
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { homedir } from "os";
|
|
@@ -5,97 +6,24 @@ const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
|
5
6
|
const CONFIG_FILE = join(CONFIG_DIR, "token-tracker.json");
|
|
6
7
|
const LOG_DIR = join(CONFIG_DIR, "logs", "token-tracker");
|
|
7
8
|
const LOG_FILE = join(LOG_DIR, "tokens.jsonl");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
toast: {
|
|
12
|
-
enabled: true,
|
|
13
|
-
duration: 3000,
|
|
14
|
-
showOnIdle: true,
|
|
15
|
-
},
|
|
16
|
-
budget: {
|
|
17
|
-
warnAt: 0.8,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
// Built-in pricing table (USD per 1M tokens) - Updated 2026-02-05
|
|
21
|
-
// Sources:
|
|
22
|
-
// - Anthropic: https://www.anthropic.com/pricing#api
|
|
23
|
-
// - OpenAI: https://openai.com/api/pricing/
|
|
24
|
-
// - DeepSeek: https://api-docs.deepseek.com/quick_start/pricing
|
|
25
|
-
// - Google: https://cloud.google.com/vertex-ai/generative-ai/pricing
|
|
26
|
-
const BUILTIN_PRICING = {
|
|
27
|
-
// Anthropic Claude (https://www.anthropic.com/pricing#api)
|
|
28
|
-
// Opus 4.5: $5 input, $25 output, cache write $6.25, cache read $0.50
|
|
29
|
-
"claude-opus-4.5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
30
|
-
// Sonnet 4.5: $3 input, $15 output (≤200K), cache write $3.75, cache read $0.30
|
|
31
|
-
"claude-sonnet-4.5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
32
|
-
// Sonnet 4: $3 input, $15 output
|
|
33
|
-
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
34
|
-
// Haiku 4.5: $1 input, $5 output, cache write $1.25, cache read $0.10
|
|
35
|
-
"claude-haiku-4.5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
36
|
-
"claude-haiku-4": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
37
|
-
// Legacy models
|
|
38
|
-
"claude-opus-4.1": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
39
|
-
"claude-opus-4": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
40
|
-
"claude-haiku-3": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
41
|
-
// OpenAI GPT (https://openai.com/api/pricing/)
|
|
42
|
-
// GPT-5.2: $1.75 input, $14 output (flagship)
|
|
43
|
-
"gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175 },
|
|
44
|
-
"gpt-5.2-pro": { input: 21, output: 168 },
|
|
45
|
-
"gpt-5-mini": { input: 0.25, output: 2, cacheRead: 0.025 },
|
|
46
|
-
"gpt-5.1": { input: 2, output: 8 },
|
|
47
|
-
"gpt-5": { input: 5, output: 15 },
|
|
48
|
-
// GPT-4.1 series (fine-tuning prices, base may differ)
|
|
49
|
-
"gpt-4.1": { input: 3, output: 12, cacheRead: 0.75 },
|
|
50
|
-
"gpt-4.1-mini": { input: 0.8, output: 3.2, cacheRead: 0.2 },
|
|
51
|
-
"gpt-4.1-nano": { input: 0.2, output: 0.8, cacheRead: 0.05 },
|
|
52
|
-
// GPT-4o series (may be deprecated)
|
|
53
|
-
"gpt-4o": { input: 2.5, output: 10 },
|
|
54
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
55
|
-
// Reasoning models
|
|
56
|
-
"o3": { input: 10, output: 40 },
|
|
57
|
-
"o3-mini": { input: 1.1, output: 4.4 },
|
|
58
|
-
"o4-mini": { input: 4, output: 16, cacheRead: 1 },
|
|
59
|
-
"o1": { input: 15, output: 60 },
|
|
60
|
-
"o1-mini": { input: 1.1, output: 4.4 },
|
|
61
|
-
// DeepSeek (https://api-docs.deepseek.com/quick_start/pricing)
|
|
62
|
-
// DeepSeek-V3.2: unified pricing for both chat and reasoner
|
|
63
|
-
// $0.28 input (cache miss), $0.028 input (cache hit), $0.42 output
|
|
64
|
-
"deepseek-chat": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
65
|
-
"deepseek-reasoner": { input: 0.28, output: 0.42, cacheRead: 0.028 },
|
|
66
|
-
// Google Gemini (https://cloud.google.com/vertex-ai/generative-ai/pricing)
|
|
67
|
-
// Gemini 3 Pro Preview: $2 input, $12 output (≤200K)
|
|
68
|
-
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2 },
|
|
69
|
-
"gemini-3-pro-preview": { input: 2, output: 12, cacheRead: 0.2 },
|
|
70
|
-
// Gemini 3 Flash Preview: $0.5 input
|
|
71
|
-
"gemini-3-flash": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
72
|
-
"gemini-3-flash-preview": { input: 0.5, output: 2, cacheRead: 0.05 },
|
|
73
|
-
// Gemini 2.5 Pro: $1.25 input, $10 output (≤200K)
|
|
74
|
-
"gemini-2.5-pro": { input: 1.25, output: 10, cacheRead: 0.125 },
|
|
75
|
-
// Gemini 2.5 Flash Lite: $0.1 input
|
|
76
|
-
"gemini-2.5-flash": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
77
|
-
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cacheRead: 0.01 },
|
|
78
|
-
// Gemini 2.0 Flash: $0.15 input
|
|
79
|
-
"gemini-2.0-flash": { input: 0.15, output: 0.6, cacheRead: 0.015 },
|
|
80
|
-
// Fallback for unknown models
|
|
81
|
-
"_default": { input: 1, output: 4 },
|
|
82
|
-
};
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Configuration
|
|
11
|
+
// ============================================================================
|
|
83
12
|
let config = DEFAULT_CONFIG;
|
|
13
|
+
let configWarnings = [];
|
|
84
14
|
function loadConfig() {
|
|
85
15
|
try {
|
|
86
16
|
if (existsSync(CONFIG_FILE)) {
|
|
87
17
|
const content = readFileSync(CONFIG_FILE, "utf-8");
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
toast: { ...DEFAULT_CONFIG.toast, ...userConfig.toast },
|
|
93
|
-
budget: { ...DEFAULT_CONFIG.budget, ...userConfig.budget },
|
|
94
|
-
};
|
|
18
|
+
const raw = JSON.parse(content);
|
|
19
|
+
const result = validateConfig(raw);
|
|
20
|
+
configWarnings = result.warnings;
|
|
21
|
+
return result.config;
|
|
95
22
|
}
|
|
96
23
|
}
|
|
97
|
-
catch
|
|
98
|
-
//
|
|
24
|
+
catch {
|
|
25
|
+
// JSON parse error - use defaults
|
|
26
|
+
configWarnings = ["Config file is not valid JSON, using defaults"];
|
|
99
27
|
}
|
|
100
28
|
return DEFAULT_CONFIG;
|
|
101
29
|
}
|
|
@@ -108,27 +36,22 @@ function getModelPricing(model, provider) {
|
|
|
108
36
|
return config.providers[provider];
|
|
109
37
|
}
|
|
110
38
|
// 2. Check user-defined model pricing
|
|
111
|
-
|
|
112
|
-
|
|
39
|
+
const configuredPricing = findModelConfigPricing(config.models, model, provider);
|
|
40
|
+
if (configuredPricing) {
|
|
41
|
+
return configuredPricing;
|
|
113
42
|
}
|
|
114
43
|
// 3. Check built-in exact match
|
|
115
44
|
if (BUILTIN_PRICING[model]) {
|
|
116
45
|
return BUILTIN_PRICING[model];
|
|
117
46
|
}
|
|
118
|
-
// 4. Try partial match in
|
|
47
|
+
// 4. Try partial match in built-in pricing
|
|
119
48
|
const modelLower = model.toLowerCase();
|
|
120
|
-
for (const [key, pricing] of Object.entries(config.models)) {
|
|
121
|
-
if (modelLower.includes(key.toLowerCase())) {
|
|
122
|
-
return pricing;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// 5. Try partial match in built-in pricing
|
|
126
49
|
for (const [key, pricing] of Object.entries(BUILTIN_PRICING)) {
|
|
127
50
|
if (key !== "_default" && modelLower.includes(key.toLowerCase())) {
|
|
128
51
|
return pricing;
|
|
129
52
|
}
|
|
130
53
|
}
|
|
131
|
-
//
|
|
54
|
+
// 5. Fallback to default
|
|
132
55
|
return BUILTIN_PRICING["_default"];
|
|
133
56
|
}
|
|
134
57
|
function calculateCost(model, provider, input, output, cacheRead = 0, cacheWrite = 0) {
|
|
@@ -157,20 +80,6 @@ function getOrCreateSessionStats(sessionId) {
|
|
|
157
80
|
}
|
|
158
81
|
return sessionStats.get(sessionId);
|
|
159
82
|
}
|
|
160
|
-
function formatCost(cost) {
|
|
161
|
-
if (cost < 0.01)
|
|
162
|
-
return `$${cost.toFixed(4)}`;
|
|
163
|
-
if (cost < 1)
|
|
164
|
-
return `$${cost.toFixed(3)}`;
|
|
165
|
-
return `$${cost.toFixed(2)}`;
|
|
166
|
-
}
|
|
167
|
-
function formatTokens(tokens) {
|
|
168
|
-
if (tokens >= 1_000_000)
|
|
169
|
-
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
170
|
-
if (tokens >= 1_000)
|
|
171
|
-
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
172
|
-
return tokens.toString();
|
|
173
|
-
}
|
|
174
83
|
// ============================================================================
|
|
175
84
|
// Deduplication
|
|
176
85
|
// ============================================================================
|
|
@@ -199,65 +108,129 @@ function logJson(data) {
|
|
|
199
108
|
const entry = JSON.stringify({ ...data, _ts: Date.now() }) + "\n";
|
|
200
109
|
appendFileSync(LOG_FILE, entry);
|
|
201
110
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
function
|
|
216
|
-
const d = new Date(date);
|
|
217
|
-
d.setDate(1);
|
|
218
|
-
d.setHours(0, 0, 0, 0);
|
|
219
|
-
return d.getTime();
|
|
220
|
-
}
|
|
221
|
-
function loadEntriesSince(since) {
|
|
111
|
+
const budgetTracker = {
|
|
112
|
+
dailySpent: 0,
|
|
113
|
+
weeklySpent: 0,
|
|
114
|
+
monthlySpent: 0,
|
|
115
|
+
dayStart: 0,
|
|
116
|
+
weekStart: 0,
|
|
117
|
+
monthStart: 0,
|
|
118
|
+
initialized: false,
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Load cost entries from JSONL since a given timestamp.
|
|
122
|
+
* Used only during initialization and period rollovers.
|
|
123
|
+
*/
|
|
124
|
+
function loadCostsSince(since) {
|
|
222
125
|
if (!existsSync(LOG_FILE))
|
|
223
|
-
return
|
|
126
|
+
return 0;
|
|
224
127
|
try {
|
|
225
128
|
const content = readFileSync(LOG_FILE, "utf-8");
|
|
226
129
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
227
|
-
|
|
130
|
+
let total = 0;
|
|
228
131
|
for (const line of lines) {
|
|
229
132
|
try {
|
|
230
133
|
const entry = JSON.parse(line);
|
|
231
134
|
if (entry.type === "tokens" && entry._ts >= since && entry.cost) {
|
|
232
|
-
|
|
135
|
+
total += entry.cost;
|
|
233
136
|
}
|
|
234
137
|
}
|
|
235
138
|
catch { }
|
|
236
139
|
}
|
|
237
|
-
return
|
|
140
|
+
return total;
|
|
238
141
|
}
|
|
239
142
|
catch {
|
|
240
|
-
return
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Initialize budgetTracker from JSONL file (called once at plugin init).
|
|
148
|
+
*/
|
|
149
|
+
function initBudgetTracker() {
|
|
150
|
+
const now = new Date();
|
|
151
|
+
budgetTracker.dayStart = getStartOfDay(now);
|
|
152
|
+
budgetTracker.weekStart = getStartOfWeek(now);
|
|
153
|
+
budgetTracker.monthStart = getStartOfMonth(now);
|
|
154
|
+
// Only load from file if budget is configured
|
|
155
|
+
const budget = config.budget;
|
|
156
|
+
if (!budget.daily && !budget.weekly && !budget.monthly) {
|
|
157
|
+
budgetTracker.initialized = true;
|
|
158
|
+
return;
|
|
241
159
|
}
|
|
160
|
+
// Load once using the earliest period boundary
|
|
161
|
+
const earliest = Math.min(budgetTracker.dayStart, budgetTracker.weekStart, budgetTracker.monthStart);
|
|
162
|
+
if (!existsSync(LOG_FILE)) {
|
|
163
|
+
budgetTracker.initialized = true;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const content = readFileSync(LOG_FILE, "utf-8");
|
|
168
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
169
|
+
let daily = 0;
|
|
170
|
+
let weekly = 0;
|
|
171
|
+
let monthly = 0;
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
try {
|
|
174
|
+
const entry = JSON.parse(line);
|
|
175
|
+
if (entry.type !== "tokens" || !entry.cost || entry._ts < earliest)
|
|
176
|
+
continue;
|
|
177
|
+
if (entry._ts >= budgetTracker.dayStart)
|
|
178
|
+
daily += entry.cost;
|
|
179
|
+
if (entry._ts >= budgetTracker.weekStart)
|
|
180
|
+
weekly += entry.cost;
|
|
181
|
+
if (entry._ts >= budgetTracker.monthStart)
|
|
182
|
+
monthly += entry.cost;
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
185
|
+
}
|
|
186
|
+
budgetTracker.dailySpent = daily;
|
|
187
|
+
budgetTracker.weeklySpent = weekly;
|
|
188
|
+
budgetTracker.monthlySpent = monthly;
|
|
189
|
+
}
|
|
190
|
+
catch { }
|
|
191
|
+
budgetTracker.initialized = true;
|
|
242
192
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Accumulate cost into budgetTracker after a new token entry is logged.
|
|
195
|
+
*/
|
|
196
|
+
function accumulateBudget(cost) {
|
|
197
|
+
if (!budgetTracker.initialized)
|
|
198
|
+
return;
|
|
199
|
+
const now = new Date();
|
|
200
|
+
const currentDayStart = getStartOfDay(now);
|
|
201
|
+
const currentWeekStart = getStartOfWeek(now);
|
|
202
|
+
const currentMonthStart = getStartOfMonth(now);
|
|
203
|
+
// Period rollover detection — reset and reload from file for accuracy
|
|
204
|
+
if (currentDayStart !== budgetTracker.dayStart) {
|
|
205
|
+
budgetTracker.dayStart = currentDayStart;
|
|
206
|
+
budgetTracker.dailySpent = loadCostsSince(currentDayStart);
|
|
207
|
+
}
|
|
208
|
+
if (currentWeekStart !== budgetTracker.weekStart) {
|
|
209
|
+
budgetTracker.weekStart = currentWeekStart;
|
|
210
|
+
budgetTracker.weeklySpent = loadCostsSince(currentWeekStart);
|
|
211
|
+
}
|
|
212
|
+
if (currentMonthStart !== budgetTracker.monthStart) {
|
|
213
|
+
budgetTracker.monthStart = currentMonthStart;
|
|
214
|
+
budgetTracker.monthlySpent = loadCostsSince(currentMonthStart);
|
|
215
|
+
}
|
|
216
|
+
budgetTracker.dailySpent += cost;
|
|
217
|
+
budgetTracker.weeklySpent += cost;
|
|
218
|
+
budgetTracker.monthlySpent += cost;
|
|
246
219
|
}
|
|
247
220
|
function checkBudgetStatus() {
|
|
248
221
|
const budget = config.budget;
|
|
249
222
|
if (!budget.daily && !budget.weekly && !budget.monthly) {
|
|
250
223
|
return null;
|
|
251
224
|
}
|
|
225
|
+
if (!budgetTracker.initialized)
|
|
226
|
+
return null;
|
|
252
227
|
const warnAt = budget.warnAt ?? 0.8;
|
|
253
|
-
const now = new Date();
|
|
254
228
|
// Check in order: daily -> weekly -> monthly (most restrictive first)
|
|
255
229
|
if (budget.daily) {
|
|
256
|
-
const
|
|
257
|
-
const percentage = spent / budget.daily;
|
|
230
|
+
const percentage = budgetTracker.dailySpent / budget.daily;
|
|
258
231
|
return {
|
|
259
232
|
period: "daily",
|
|
260
|
-
spent,
|
|
233
|
+
spent: budgetTracker.dailySpent,
|
|
261
234
|
limit: budget.daily,
|
|
262
235
|
percentage,
|
|
263
236
|
exceeded: percentage >= 1,
|
|
@@ -265,11 +238,10 @@ function checkBudgetStatus() {
|
|
|
265
238
|
};
|
|
266
239
|
}
|
|
267
240
|
if (budget.weekly) {
|
|
268
|
-
const
|
|
269
|
-
const percentage = spent / budget.weekly;
|
|
241
|
+
const percentage = budgetTracker.weeklySpent / budget.weekly;
|
|
270
242
|
return {
|
|
271
243
|
period: "weekly",
|
|
272
|
-
spent,
|
|
244
|
+
spent: budgetTracker.weeklySpent,
|
|
273
245
|
limit: budget.weekly,
|
|
274
246
|
percentage,
|
|
275
247
|
exceeded: percentage >= 1,
|
|
@@ -277,11 +249,10 @@ function checkBudgetStatus() {
|
|
|
277
249
|
};
|
|
278
250
|
}
|
|
279
251
|
if (budget.monthly) {
|
|
280
|
-
const
|
|
281
|
-
const percentage = spent / budget.monthly;
|
|
252
|
+
const percentage = budgetTracker.monthlySpent / budget.monthly;
|
|
282
253
|
return {
|
|
283
254
|
period: "monthly",
|
|
284
|
-
spent,
|
|
255
|
+
spent: budgetTracker.monthlySpent,
|
|
285
256
|
limit: budget.monthly,
|
|
286
257
|
percentage,
|
|
287
258
|
exceeded: percentage >= 1,
|
|
@@ -296,9 +267,25 @@ function formatBudgetMessage(status) {
|
|
|
296
267
|
return `${periodLabel}: ${formatCost(status.spent)}/${formatCost(status.limit)} (${pct}%)`;
|
|
297
268
|
}
|
|
298
269
|
export const TokenTrackerPlugin = async ({ directory, client }) => {
|
|
299
|
-
// Load config on plugin init
|
|
270
|
+
// Load config on plugin init (with validation)
|
|
300
271
|
config = loadConfig();
|
|
272
|
+
// Initialize in-memory budget tracker (reads JSONL once)
|
|
273
|
+
initBudgetTracker();
|
|
301
274
|
logJson({ type: "init", directory, configLoaded: existsSync(CONFIG_FILE) });
|
|
275
|
+
// Show config validation warnings via Toast
|
|
276
|
+
if (configWarnings.length > 0) {
|
|
277
|
+
try {
|
|
278
|
+
await client.tui.showToast({
|
|
279
|
+
body: {
|
|
280
|
+
title: "Token Tracker: config warning",
|
|
281
|
+
message: configWarnings.join("; "),
|
|
282
|
+
variant: "warning",
|
|
283
|
+
duration: 5000,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch { }
|
|
288
|
+
}
|
|
302
289
|
return {
|
|
303
290
|
event: async ({ event }) => {
|
|
304
291
|
try {
|
|
@@ -351,6 +338,8 @@ export const TokenTrackerPlugin = async ({ directory, client }) => {
|
|
|
351
338
|
cacheWrite,
|
|
352
339
|
cost,
|
|
353
340
|
});
|
|
341
|
+
// Accumulate cost into in-memory budget tracker
|
|
342
|
+
accumulateBudget(cost);
|
|
354
343
|
// Show toast for this message
|
|
355
344
|
if (config.toast.enabled) {
|
|
356
345
|
const totalTokens = input + output;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface ModelPricing {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
cacheRead?: number;
|
|
5
|
+
cacheWrite?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderModelPricingMap {
|
|
8
|
+
[provider: string]: ModelPricing;
|
|
9
|
+
}
|
|
10
|
+
export declare const BUILTIN_PRICING: Record<string, ModelPricing>;
|
|
11
|
+
export declare function formatCost(cost: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Format token count for display.
|
|
14
|
+
* @param tokens - raw token count
|
|
15
|
+
* @param millionDecimals - decimal places for M-level values (default 1)
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatTokens(tokens: number, millionDecimals?: number): string;
|
|
18
|
+
export declare function getStartOfDay(date?: Date): number;
|
|
19
|
+
export declare function getStartOfWeek(date?: Date): number;
|
|
20
|
+
export declare function getStartOfMonth(date?: Date): number;
|
|
21
|
+
export interface ToastConfig {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
duration: number;
|
|
24
|
+
showOnIdle: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface BudgetConfig {
|
|
27
|
+
daily?: number;
|
|
28
|
+
weekly?: number;
|
|
29
|
+
monthly?: number;
|
|
30
|
+
warnAt: number;
|
|
31
|
+
}
|
|
32
|
+
export interface TrackerConfig {
|
|
33
|
+
providers: Record<string, ModelPricing>;
|
|
34
|
+
models: Record<string, ModelPricing | ProviderModelPricingMap>;
|
|
35
|
+
toast: ToastConfig;
|
|
36
|
+
budget: BudgetConfig;
|
|
37
|
+
}
|
|
38
|
+
export declare const DEFAULT_CONFIG: TrackerConfig;
|
|
39
|
+
export interface ConfigValidationResult {
|
|
40
|
+
config: TrackerConfig;
|
|
41
|
+
warnings: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate and normalize raw config (from JSON.parse).
|
|
45
|
+
* Invalid fields are silently corrected to defaults with warnings.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateConfig(raw: unknown): ConfigValidationResult;
|
|
48
|
+
export declare function findModelConfigPricing(models: TrackerConfig["models"], model: string, provider: string): ModelPricing | undefined;
|