pi-free 1.0.8 → 2.0.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/README.md +95 -46
  3. package/config.ts +165 -120
  4. package/constants.ts +22 -61
  5. package/index.ts +186 -0
  6. package/lib/json-persistence.ts +11 -10
  7. package/lib/logger.ts +2 -2
  8. package/lib/model-enhancer.ts +20 -20
  9. package/lib/open-browser.ts +41 -0
  10. package/lib/provider-cache.ts +106 -0
  11. package/lib/registry.ts +144 -0
  12. package/package.json +67 -82
  13. package/provider-factory.ts +25 -41
  14. package/provider-failover/benchmark-lookup.ts +247 -0
  15. package/provider-failover/benchmarks-chunk-0.ts +2010 -0
  16. package/provider-failover/benchmarks-chunk-1.ts +1988 -0
  17. package/provider-failover/benchmarks-chunk-2.ts +2010 -0
  18. package/provider-failover/benchmarks-chunk-3.ts +2010 -0
  19. package/provider-failover/benchmarks-chunk-4.ts +1969 -0
  20. package/provider-failover/hardcoded-benchmarks.ts +22 -10025
  21. package/provider-helper.ts +38 -37
  22. package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
  23. package/providers/cline/cline-models.ts +128 -0
  24. package/providers/{cline.ts → cline/cline.ts} +300 -257
  25. package/providers/cloudflare/cloudflare.ts +368 -0
  26. package/providers/dynamic-built-in/index.ts +513 -0
  27. package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
  28. package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
  29. package/providers/kilo/kilo.ts +235 -0
  30. package/providers/{modal.ts → modal/modal.ts} +4 -3
  31. package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
  32. package/providers/ollama/ollama.ts +172 -0
  33. package/providers/opencode-session.ts +34 -34
  34. package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
  35. package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
  36. package/providers/qwen/qwen.ts +202 -0
  37. package/provider-failover/auto-switch.ts +0 -350
  38. package/provider-failover/errors.ts +0 -275
  39. package/provider-failover/index.ts +0 -238
  40. package/providers/cline-models.ts +0 -77
  41. package/providers/factory.ts +0 -125
  42. package/providers/fireworks.ts +0 -49
  43. package/providers/go.ts +0 -216
  44. package/providers/kilo.ts +0 -146
  45. package/providers/mistral.ts +0 -144
  46. package/providers/ollama.ts +0 -113
  47. package/providers/openrouter.ts +0 -175
  48. package/providers/qwen.ts +0 -127
  49. package/providers/zen.ts +0 -371
  50. package/usage/commands.ts +0 -17
  51. package/usage/cumulative.ts +0 -193
  52. package/usage/formatters.ts +0 -115
  53. package/usage/index.ts +0 -46
  54. package/usage/limits.ts +0 -148
  55. package/usage/metrics.ts +0 -222
  56. package/usage/sessions.ts +0 -355
  57. package/usage/store.ts +0 -99
  58. package/usage/tracking.ts +0 -329
  59. package/usage/types.ts +0 -26
  60. package/usage/widget.ts +0 -90
  61. package/widget/data.ts +0 -113
  62. package/widget/format.ts +0 -26
  63. package/widget/render.ts +0 -117
package/usage/metrics.ts DELETED
@@ -1,222 +0,0 @@
1
- /**
2
- * Provider usage metrics - tracks rate limits and usage for each provider.
3
- */
4
-
5
- import { OPENCODE_API_KEY, OPENROUTER_API_KEY } from "../config.ts";
6
- import {
7
- BASE_URL_OPENROUTER,
8
- BASE_URL_ZEN,
9
- DEFAULT_FETCH_TIMEOUT_MS,
10
- } from "../constants.ts";
11
- import { fetchWithTimeout, logWarning } from "../lib/util.ts";
12
-
13
- // =============================================================================
14
- // Types
15
- // =============================================================================
16
-
17
- export interface ProviderMetrics {
18
- provider: string;
19
- rateLimit?: {
20
- requestsPerMinute?: number;
21
- requestsPerDay?: number;
22
- remainingToday?: number;
23
- };
24
- balance?: number;
25
- credits?: number;
26
- requestsToday: number;
27
- lastUpdated: number;
28
- }
29
-
30
- // =============================================================================
31
- // Request counting (in-memory per session)
32
- // =============================================================================
33
-
34
- const requestCounts: Map<string, number> = new Map();
35
- const dailyRequestCounts: Map<string, { count: number; date: string }> =
36
- new Map();
37
-
38
- function getTodayDate(): string {
39
- return new Date().toISOString().split("T")[0];
40
- }
41
-
42
- export function incrementRequestCount(provider: string): void {
43
- const key = `${provider}_session`;
44
- const count = requestCounts.get(key) || 0;
45
- requestCounts.set(key, count + 1);
46
-
47
- // Daily counter
48
- const today = getTodayDate();
49
- const dailyKey = `${provider}_daily`;
50
- const daily = dailyRequestCounts.get(dailyKey);
51
- if (daily && daily.date === today) {
52
- daily.count++;
53
- } else {
54
- dailyRequestCounts.set(dailyKey, { count: 1, date: today });
55
- }
56
- }
57
-
58
- export function getRequestCount(provider: string): number {
59
- return requestCounts.get(`${provider}_session`) || 0;
60
- }
61
-
62
- export function getDailyRequestCount(provider: string): number {
63
- const today = getTodayDate();
64
- const daily = dailyRequestCounts.get(`${provider}_daily`);
65
- return daily && daily.date === today ? daily.count : 0;
66
- }
67
-
68
- // =============================================================================
69
- // OpenRouter metrics
70
- // =============================================================================
71
-
72
- interface OpenRouterKeyResponse {
73
- usage?: {
74
- "24h": number;
75
- "7d": number;
76
- total: number;
77
- };
78
- limit?: {
79
- "24h": number;
80
- "7d": number;
81
- total: number;
82
- };
83
- soft_limit?: boolean;
84
- }
85
-
86
- interface OpenRouterCreditsResponse {
87
- data?: {
88
- credits_purchased: number;
89
- credits_used: number;
90
- };
91
- }
92
-
93
- export async function fetchOpenRouterMetrics(): Promise<ProviderMetrics | null> {
94
- if (!OPENROUTER_API_KEY) return null;
95
-
96
- try {
97
- // Fetch both key info and credits in parallel
98
- const [keyResponse, creditsResponse] = await Promise.all([
99
- fetchWithTimeout(
100
- `${BASE_URL_OPENROUTER}/key`,
101
- {
102
- headers: {
103
- Authorization: `Bearer ${OPENROUTER_API_KEY}`,
104
- "User-Agent": "pi-free-providers",
105
- },
106
- },
107
- DEFAULT_FETCH_TIMEOUT_MS,
108
- ),
109
- fetchWithTimeout(
110
- `${BASE_URL_OPENROUTER}/credits`,
111
- {
112
- headers: {
113
- Authorization: `Bearer ${OPENROUTER_API_KEY}`,
114
- "User-Agent": "pi-free-providers",
115
- },
116
- },
117
- DEFAULT_FETCH_TIMEOUT_MS,
118
- ),
119
- ]);
120
-
121
- let limit24h: number | undefined;
122
- let usage24h: number | undefined;
123
- let credits = 0;
124
-
125
- if (keyResponse.ok) {
126
- const keyData = (await keyResponse.json()) as OpenRouterKeyResponse;
127
- limit24h = keyData.limit?.["24h"];
128
- usage24h = keyData.usage?.["24h"];
129
- }
130
-
131
- if (creditsResponse.ok) {
132
- const creditsData =
133
- (await creditsResponse.json()) as OpenRouterCreditsResponse;
134
- if (creditsData.data) {
135
- credits =
136
- creditsData.data.credits_purchased - creditsData.data.credits_used;
137
- }
138
- }
139
-
140
- const dailyCount = getDailyRequestCount("openrouter");
141
-
142
- return {
143
- provider: "openrouter",
144
- rateLimit: {
145
- requestsPerMinute: 20, // Fixed for free models
146
- requestsPerDay: limit24h,
147
- remainingToday: limit24h && usage24h ? limit24h - usage24h : undefined,
148
- },
149
- credits,
150
- requestsToday: dailyCount,
151
- lastUpdated: Date.now(),
152
- };
153
- } catch (error) {
154
- logWarning("openrouter", "Failed to fetch metrics", error);
155
- return null;
156
- }
157
- }
158
-
159
- // =============================================================================
160
- // OpenCode metrics (balance only - no rate limits known)
161
- // =============================================================================
162
-
163
- export async function fetchOpenCodeMetrics(): Promise<ProviderMetrics | null> {
164
- if (!OPENCODE_API_KEY) return null;
165
-
166
- try {
167
- const response = await fetchWithTimeout(
168
- `${BASE_URL_ZEN}/user`,
169
- {
170
- headers: {
171
- Authorization: `Bearer ${OPENCODE_API_KEY}`,
172
- "User-Agent": "pi-free-providers",
173
- },
174
- },
175
- DEFAULT_FETCH_TIMEOUT_MS,
176
- );
177
-
178
- if (!response.ok) {
179
- return null;
180
- }
181
-
182
- const data = (await response.json()) as {
183
- balance?: number;
184
- credits?: number;
185
- };
186
- const dailyCount = getDailyRequestCount("opencode");
187
-
188
- return {
189
- provider: "opencode",
190
- balance: data.balance,
191
- credits: data.credits,
192
- requestsToday: dailyCount,
193
- lastUpdated: Date.now(),
194
- };
195
- } catch (error) {
196
- logWarning("opencode", "Failed to fetch metrics", error);
197
- return null;
198
- }
199
- }
200
-
201
- // =============================================================================
202
- // Cached metrics storage
203
- // =============================================================================
204
-
205
- const metricsCache: Map<string, { data: ProviderMetrics; timestamp: number }> =
206
- new Map();
207
- const CACHE_TTL_MS = 60_000; // 1 minute cache
208
-
209
- export function getCachedMetrics(provider: string): ProviderMetrics | null {
210
- const cached = metricsCache.get(provider);
211
- if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
212
- return cached.data;
213
- }
214
- return null;
215
- }
216
-
217
- export function setCachedMetrics(
218
- provider: string,
219
- metrics: ProviderMetrics,
220
- ): void {
221
- metricsCache.set(provider, { data: metrics, timestamp: Date.now() });
222
- }
package/usage/sessions.ts DELETED
@@ -1,355 +0,0 @@
1
- /**
2
- * Session file parsing - extracts historical usage data from Pi session files
3
- *
4
- * Pi stores sessions in ~/.pi/agent/sessions/{cwd-hash}/*.jsonl
5
- * Each line is a JSON entry with type "session" or "message"
6
- */
7
-
8
- import { readdir, readFile } from "node:fs/promises";
9
- import { homedir } from "node:os";
10
- import { join } from "node:path";
11
- import { createLogger } from "../lib/logger.ts";
12
-
13
- const _logger = createLogger("session-parser");
14
-
15
- export interface SessionMessage {
16
- provider: string;
17
- model: string;
18
- tokensIn: number;
19
- tokensOut: number;
20
- cacheRead: number;
21
- cacheWrite: number;
22
- cost: number;
23
- timestamp: number;
24
- }
25
-
26
- export interface ParsedSession {
27
- sessionId: string;
28
- messages: SessionMessage[];
29
- }
30
-
31
- function getSessionsDir(): string {
32
- const agentDir =
33
- process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
34
- return join(agentDir, "sessions");
35
- }
36
-
37
- async function getAllSessionFiles(signal?: AbortSignal): Promise<string[]> {
38
- const sessionsDir = getSessionsDir();
39
- const files: string[] = [];
40
-
41
- try {
42
- const cwdDirs = await readdir(sessionsDir, { withFileTypes: true });
43
- for (const dir of cwdDirs) {
44
- if (signal?.aborted) return files;
45
- if (!dir.isDirectory()) continue;
46
- const cwdPath = join(sessionsDir, dir.name);
47
- try {
48
- const sessionFiles = await readdir(cwdPath);
49
- for (const file of sessionFiles) {
50
- if (file.endsWith(".jsonl")) {
51
- files.push(join(cwdPath, file));
52
- }
53
- }
54
- } catch {
55
- // Skip directories we can't read
56
- }
57
- }
58
- } catch {
59
- // Return empty if we can't read sessions dir
60
- }
61
-
62
- return files;
63
- }
64
-
65
- async function parseSessionFile(
66
- filePath: string,
67
- seenHashes: Set<string>,
68
- signal?: AbortSignal,
69
- ): Promise<ParsedSession | null> {
70
- try {
71
- const content = await readFile(filePath, "utf8");
72
- if (signal?.aborted) return null;
73
-
74
- const lines = content.trim().split("\n");
75
- const messages: SessionMessage[] = [];
76
- let sessionId = "";
77
-
78
- for (let i = 0; i < lines.length; i++) {
79
- if (signal?.aborted) return null;
80
- if (i % 500 === 0) {
81
- await new Promise<void>((resolve) => setImmediate(resolve));
82
- }
83
-
84
- const line = lines[i]!;
85
- if (!line.trim()) continue;
86
-
87
- try {
88
- const entry = JSON.parse(line);
89
-
90
- if (entry.type === "session") {
91
- sessionId = entry.id;
92
- } else if (
93
- entry.type === "message" &&
94
- entry.message?.role === "assistant"
95
- ) {
96
- const msg = entry.message;
97
- if (msg.usage && msg.provider && msg.model) {
98
- const tokensIn = msg.usage.input || 0;
99
- const tokensOut = msg.usage.output || 0;
100
- const cacheRead = msg.usage.cacheRead || 0;
101
- const cacheWrite = msg.usage.cacheWrite || 0;
102
- const cost = msg.usage.cost?.total || 0;
103
-
104
- const fallbackTs = entry.timestamp
105
- ? new Date(entry.timestamp).getTime()
106
- : 0;
107
- const timestamp =
108
- msg.timestamp || (Number.isNaN(fallbackTs) ? 0 : fallbackTs);
109
-
110
- const totalTokens = tokensIn + tokensOut + cacheRead + cacheWrite;
111
- const hash = `${timestamp}:${totalTokens}`;
112
- if (seenHashes.has(hash)) continue;
113
- seenHashes.add(hash);
114
-
115
- messages.push({
116
- provider: msg.provider,
117
- model: msg.model,
118
- tokensIn,
119
- tokensOut,
120
- cacheRead,
121
- cacheWrite,
122
- cost,
123
- timestamp,
124
- });
125
- }
126
- }
127
- } catch {
128
- // Skip malformed lines
129
- }
130
- }
131
-
132
- return sessionId ? { sessionId, messages } : null;
133
- } catch {
134
- return null;
135
- }
136
- }
137
-
138
- export type TimePeriod = "today" | "thisWeek" | "allTime";
139
-
140
- export interface TimeRange {
141
- start: number;
142
- end?: number;
143
- }
144
-
145
- export function getTimeRange(period: TimePeriod): TimeRange {
146
- const now = Date.now();
147
- const startOfToday = new Date();
148
- startOfToday.setHours(0, 0, 0, 0);
149
-
150
- switch (period) {
151
- case "today":
152
- return { start: startOfToday.getTime(), end: now };
153
- case "thisWeek": {
154
- const startOfWeek = new Date();
155
- const dayOfWeek = startOfWeek.getDay();
156
- const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
157
- startOfWeek.setDate(startOfWeek.getDate() - daysSinceMonday);
158
- startOfWeek.setHours(0, 0, 0, 0);
159
- return { start: startOfWeek.getTime(), end: now };
160
- }
161
- case "allTime":
162
- return { start: 0, end: now };
163
- }
164
- }
165
-
166
- export function filterByTimeRange(
167
- messages: SessionMessage[],
168
- range: TimeRange,
169
- ): SessionMessage[] {
170
- return messages.filter((m) => {
171
- if (m.timestamp < range.start) return false;
172
- if (range.end && m.timestamp > range.end) return false;
173
- return true;
174
- });
175
- }
176
-
177
- export interface ModelStats {
178
- count: number;
179
- tokensIn: number;
180
- tokensOut: number;
181
- cacheRead: number;
182
- cacheWrite: number;
183
- cost: number;
184
- }
185
-
186
- export interface ProviderStats {
187
- messages: number;
188
- tokensIn: number;
189
- tokensOut: number;
190
- cacheRead: number;
191
- cacheWrite: number;
192
- cost: number;
193
- models: Record<string, ModelStats>;
194
- }
195
-
196
- export interface SessionFileStats {
197
- totalMessages: number;
198
- totalTokensIn: number;
199
- totalTokensOut: number;
200
- totalCacheRead: number;
201
- totalCacheWrite: number;
202
- totalCost: number;
203
- providers: Record<string, ProviderStats>;
204
- sessions: Set<string>;
205
- }
206
-
207
- export function createEmptyStats(): SessionFileStats {
208
- return {
209
- totalMessages: 0,
210
- totalTokensIn: 0,
211
- totalTokensOut: 0,
212
- totalCacheRead: 0,
213
- totalCacheWrite: 0,
214
- totalCost: 0,
215
- providers: {},
216
- sessions: new Set(),
217
- };
218
- }
219
-
220
- export function aggregateMessages(
221
- messages: SessionMessage[],
222
- sessionId: string,
223
- ): SessionFileStats {
224
- const stats = createEmptyStats();
225
- stats.sessions.add(sessionId);
226
-
227
- for (const msg of messages) {
228
- stats.totalMessages++;
229
- stats.totalTokensIn += msg.tokensIn;
230
- stats.totalTokensOut += msg.tokensOut;
231
- stats.totalCacheRead += msg.cacheRead;
232
- stats.totalCacheWrite += msg.cacheWrite;
233
- stats.totalCost += msg.cost;
234
-
235
- if (!stats.providers[msg.provider]) {
236
- stats.providers[msg.provider] = {
237
- messages: 0,
238
- tokensIn: 0,
239
- tokensOut: 0,
240
- cacheRead: 0,
241
- cacheWrite: 0,
242
- cost: 0,
243
- models: {},
244
- };
245
- }
246
- const provider = stats.providers[msg.provider]!;
247
- provider.messages++;
248
- provider.tokensIn += msg.tokensIn;
249
- provider.tokensOut += msg.tokensOut;
250
- provider.cacheRead += msg.cacheRead;
251
- provider.cacheWrite += msg.cacheWrite;
252
- provider.cost += msg.cost;
253
-
254
- if (!provider.models[msg.model]) {
255
- provider.models[msg.model] = {
256
- count: 0,
257
- tokensIn: 0,
258
- tokensOut: 0,
259
- cacheRead: 0,
260
- cacheWrite: 0,
261
- cost: 0,
262
- };
263
- }
264
- const model = provider.models[msg.model]!;
265
- model.count++;
266
- model.tokensIn += msg.tokensIn;
267
- model.tokensOut += msg.tokensOut;
268
- model.cacheRead += msg.cacheRead;
269
- model.cacheWrite += msg.cacheWrite;
270
- model.cost += msg.cost;
271
- }
272
-
273
- return stats;
274
- }
275
-
276
- export async function collectSessionFileStats(
277
- period: TimePeriod = "allTime",
278
- signal?: AbortSignal,
279
- ): Promise<SessionFileStats> {
280
- const range = getTimeRange(period);
281
- const allStats = createEmptyStats();
282
-
283
- const sessionFiles = await getAllSessionFiles(signal);
284
- if (signal?.aborted) return allStats;
285
-
286
- const seenHashes = new Set<string>();
287
-
288
- for (const filePath of sessionFiles) {
289
- if (signal?.aborted) return allStats;
290
-
291
- const parsed = await parseSessionFile(filePath, seenHashes, signal);
292
- if (signal?.aborted) return allStats;
293
- if (!parsed) continue;
294
-
295
- const filteredMessages = filterByTimeRange(parsed.messages, range);
296
- if (filteredMessages.length === 0) continue;
297
-
298
- const fileStats = aggregateMessages(filteredMessages, parsed.sessionId);
299
-
300
- allStats.sessions.add(parsed.sessionId);
301
- allStats.totalMessages += fileStats.totalMessages;
302
- allStats.totalTokensIn += fileStats.totalTokensIn;
303
- allStats.totalTokensOut += fileStats.totalTokensOut;
304
- allStats.totalCacheRead += fileStats.totalCacheRead;
305
- allStats.totalCacheWrite += fileStats.totalCacheWrite;
306
- allStats.totalCost += fileStats.totalCost;
307
-
308
- for (const [providerName, providerStats] of Object.entries(
309
- fileStats.providers,
310
- )) {
311
- if (!allStats.providers[providerName]) {
312
- allStats.providers[providerName] = {
313
- messages: 0,
314
- tokensIn: 0,
315
- tokensOut: 0,
316
- cacheRead: 0,
317
- cacheWrite: 0,
318
- cost: 0,
319
- models: {},
320
- };
321
- }
322
- const allProvider = allStats.providers[providerName]!;
323
- allProvider.messages += providerStats.messages;
324
- allProvider.tokensIn += providerStats.tokensIn;
325
- allProvider.tokensOut += providerStats.tokensOut;
326
- allProvider.cacheRead += providerStats.cacheRead;
327
- allProvider.cacheWrite += providerStats.cacheWrite;
328
- allProvider.cost += providerStats.cost;
329
-
330
- for (const [modelName, modelStats] of Object.entries(
331
- providerStats.models,
332
- )) {
333
- if (!allProvider.models[modelName]) {
334
- allProvider.models[modelName] = {
335
- count: 0,
336
- tokensIn: 0,
337
- tokensOut: 0,
338
- cacheRead: 0,
339
- cacheWrite: 0,
340
- cost: 0,
341
- };
342
- }
343
- const allModel = allProvider.models[modelName]!;
344
- allModel.count += modelStats.count;
345
- allModel.tokensIn += modelStats.tokensIn;
346
- allModel.tokensOut += modelStats.tokensOut;
347
- allModel.cacheRead += modelStats.cacheRead;
348
- allModel.cacheWrite += modelStats.cacheWrite;
349
- allModel.cost += modelStats.cost;
350
- }
351
- }
352
- }
353
-
354
- return allStats;
355
- }
package/usage/store.ts DELETED
@@ -1,99 +0,0 @@
1
- /**
2
- * Persistent cumulative usage tracking per provider.
3
- *
4
- * Stored at ~/./.pi/free-usage.json — survives across sessions.
5
- * Updated on each turn_end via provider-helper.ts.
6
- *
7
- * This answers: "how much free value have I gotten over time?"
8
- */
9
-
10
- import { join } from "node:path";
11
- import { createJSONStore } from "../lib/json-persistence.js";
12
- import { createLogger } from "../lib/logger.js";
13
-
14
- // =============================================================================
15
- // Types
16
- // =============================================================================
17
-
18
- export interface ProviderCumulativeUsage {
19
- /** Total input tokens across all sessions. */
20
- tokensIn: number;
21
- /** Total output tokens across all sessions. */
22
- tokensOut: number;
23
- /** Total requests across all sessions. */
24
- requests: number;
25
- /** Total cost that would have been charged on a paid tier. */
26
- costEquivalent: number;
27
- /** ISO date of first tracked request. */
28
- firstUsed: string;
29
- /** ISO date of last tracked request. */
30
- lastUsed: string;
31
- }
32
-
33
- export interface CumulativeUsageStore {
34
- [provider: string]: ProviderCumulativeUsage;
35
- }
36
-
37
- // =============================================================================
38
- // Storage
39
- // =============================================================================
40
-
41
- const PI_DIR = join(process.env.HOME || process.env.USERPROFILE || "", ".pi");
42
- const USAGE_PATH = join(PI_DIR, "free-usage.json");
43
-
44
- const logger = createLogger("usage-store");
45
-
46
- const store = createJSONStore<CumulativeUsageStore>(USAGE_PATH, {});
47
-
48
- // =============================================================================
49
- // API
50
- // =============================================================================
51
-
52
- /** Record a turn's token usage for a provider. */
53
- export function recordTurn(
54
- provider: string,
55
- tokensIn: number,
56
- tokensOut: number,
57
- costEquivalent: number,
58
- ): void {
59
- const data = store.load();
60
- const now = new Date().toISOString();
61
- const existing = data[provider];
62
-
63
- if (existing) {
64
- existing.tokensIn += tokensIn;
65
- existing.tokensOut += tokensOut;
66
- existing.requests += 1;
67
- existing.costEquivalent += costEquivalent;
68
- existing.lastUsed = now;
69
- } else {
70
- data[provider] = {
71
- tokensIn,
72
- tokensOut,
73
- requests: 1,
74
- costEquivalent,
75
- firstUsed: now,
76
- lastUsed: now,
77
- };
78
- }
79
-
80
- store.save(data);
81
- logger.debug("recorded turn", {
82
- provider,
83
- tokensIn,
84
- tokensOut,
85
- costEquivalent,
86
- });
87
- }
88
-
89
- /** Get cumulative usage for a specific provider. */
90
- export function getCumulativeUsage(
91
- provider: string,
92
- ): ProviderCumulativeUsage | null {
93
- return store.load()[provider] ?? null;
94
- }
95
-
96
- /** Get all cumulative usage data. */
97
- export function getAllCumulativeUsage(): CumulativeUsageStore {
98
- return store.load();
99
- }