opencode-token-tracker 1.5.4 → 1.5.5
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/dist/bin/opencode-tokens.js +0 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +48 -3
- package/dist/test/shared.test.js +79 -0
- package/package.json +1 -1
|
File without changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
type ProviderFamily = "anthropic" | "openai" | "deepseek" | "google" | "other";
|
|
3
|
+
export declare function getProviderFamily(model: string, provider: string): ProviderFamily;
|
|
4
|
+
export declare function calculateCost(model: string, provider: string, input: number, output: number, cacheRead?: number, cacheWrite?: number): number;
|
|
2
5
|
export declare const TokenTrackerPlugin: Plugin;
|
|
3
6
|
export default TokenTrackerPlugin;
|
package/dist/index.js
CHANGED
|
@@ -76,14 +76,59 @@ function getModelPricing(model, provider) {
|
|
|
76
76
|
// 5. Fallback to default
|
|
77
77
|
return BUILTIN_PRICING["_default"];
|
|
78
78
|
}
|
|
79
|
-
function
|
|
79
|
+
export function getProviderFamily(model, provider) {
|
|
80
|
+
const p = provider.toLowerCase();
|
|
81
|
+
const m = model.toLowerCase();
|
|
82
|
+
if (p.includes("anthropic") || m.startsWith("claude-")) {
|
|
83
|
+
return "anthropic";
|
|
84
|
+
}
|
|
85
|
+
if (p.includes("openai") ||
|
|
86
|
+
m.startsWith("gpt-") ||
|
|
87
|
+
m.startsWith("o1-") ||
|
|
88
|
+
m.startsWith("o3-") ||
|
|
89
|
+
m.startsWith("o4-") ||
|
|
90
|
+
m === "o3" ||
|
|
91
|
+
m === "o1") {
|
|
92
|
+
return "openai";
|
|
93
|
+
}
|
|
94
|
+
if (p.includes("deepseek") || m.includes("deepseek")) {
|
|
95
|
+
return "deepseek";
|
|
96
|
+
}
|
|
97
|
+
if (p.includes("google") || p.includes("vertex") || m.startsWith("gemini-")) {
|
|
98
|
+
return "google";
|
|
99
|
+
}
|
|
100
|
+
return "other";
|
|
101
|
+
}
|
|
102
|
+
export function calculateCost(model, provider, input, output, cacheRead = 0, cacheWrite = 0) {
|
|
80
103
|
const pricing = getModelPricing(model, provider);
|
|
104
|
+
const family = getProviderFamily(model, provider);
|
|
105
|
+
let defaultCacheReadRate = 0.5; // Default 50% discount (OpenAI style)
|
|
106
|
+
let defaultCacheWriteRate = 0; // Default free cache writing
|
|
107
|
+
if (family === "anthropic") {
|
|
108
|
+
defaultCacheReadRate = 0.1;
|
|
109
|
+
defaultCacheWriteRate = 1.25;
|
|
110
|
+
}
|
|
111
|
+
else if (family === "deepseek" || family === "google") {
|
|
112
|
+
defaultCacheReadRate = 0.1;
|
|
113
|
+
defaultCacheWriteRate = 0;
|
|
114
|
+
}
|
|
115
|
+
else if (family === "openai") {
|
|
116
|
+
defaultCacheReadRate = 0.5;
|
|
117
|
+
defaultCacheWriteRate = 0;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// "other" / general default
|
|
121
|
+
defaultCacheReadRate = 0.5;
|
|
122
|
+
defaultCacheWriteRate = 0;
|
|
123
|
+
}
|
|
124
|
+
const finalCacheReadPrice = pricing.cacheRead ?? (pricing.input * defaultCacheReadRate);
|
|
125
|
+
const finalCacheWritePrice = pricing.cacheWrite ?? (pricing.input * defaultCacheWriteRate);
|
|
81
126
|
// Billable input = total input - cache read (cached tokens are charged at cache rate)
|
|
82
127
|
const billableInput = Math.max(0, input - cacheRead);
|
|
83
128
|
const inputCost = (billableInput / 1_000_000) * pricing.input;
|
|
84
129
|
const outputCost = (output / 1_000_000) * pricing.output;
|
|
85
|
-
const cacheReadCost = (cacheRead / 1_000_000) *
|
|
86
|
-
const cacheWriteCost = (cacheWrite / 1_000_000) *
|
|
130
|
+
const cacheReadCost = (cacheRead / 1_000_000) * finalCacheReadPrice;
|
|
131
|
+
const cacheWriteCost = (cacheWrite / 1_000_000) * finalCacheWritePrice;
|
|
87
132
|
return inputCost + outputCost + cacheReadCost + cacheWriteCost;
|
|
88
133
|
}
|
|
89
134
|
const sessionStats = new Map();
|
package/dist/test/shared.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
2
|
import { strict as assert } from "node:assert";
|
|
3
3
|
import { BUILTIN_PRICING, DEFAULT_CONFIG, findModelConfigPricing, formatCost, formatTokens, getStartOfDay, getStartOfWeek, getStartOfMonth, validateConfig, } from "../lib/shared.js";
|
|
4
|
+
import { getProviderFamily, calculateCost } from "../index.js";
|
|
4
5
|
// ============================================================================
|
|
5
6
|
// formatCost
|
|
6
7
|
// ============================================================================
|
|
@@ -376,3 +377,81 @@ describe("findModelConfigPricing", () => {
|
|
|
376
377
|
assert.deepEqual(findModelConfigPricing(result.config.models, "prefix/my-model", "any-provider"), { input: 1, output: 2 });
|
|
377
378
|
});
|
|
378
379
|
});
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// getProviderFamily
|
|
382
|
+
// ============================================================================
|
|
383
|
+
describe("getProviderFamily", () => {
|
|
384
|
+
it("should detect anthropic for various combinations", () => {
|
|
385
|
+
assert.equal(getProviderFamily("claude-opus-4.6", "openai"), "anthropic");
|
|
386
|
+
assert.equal(getProviderFamily("some-model", "anthropic"), "anthropic");
|
|
387
|
+
assert.equal(getProviderFamily("claude-3-5-sonnet", "openrouter"), "anthropic");
|
|
388
|
+
});
|
|
389
|
+
it("should detect openai for various combinations", () => {
|
|
390
|
+
assert.equal(getProviderFamily("gpt-4o", "unknown"), "openai");
|
|
391
|
+
assert.equal(getProviderFamily("o1-mini", "openrouter"), "openai");
|
|
392
|
+
assert.equal(getProviderFamily("o3", "together"), "openai");
|
|
393
|
+
assert.equal(getProviderFamily("some-model", "openai"), "openai");
|
|
394
|
+
});
|
|
395
|
+
it("should detect deepseek for various combinations", () => {
|
|
396
|
+
assert.equal(getProviderFamily("deepseek-chat", "siliconflow"), "deepseek");
|
|
397
|
+
assert.equal(getProviderFamily("deepseek-reasoner", "deepseek"), "deepseek");
|
|
398
|
+
assert.equal(getProviderFamily("deepseek/deepseek-r1", "openrouter"), "deepseek");
|
|
399
|
+
});
|
|
400
|
+
it("should detect google for various combinations", () => {
|
|
401
|
+
assert.equal(getProviderFamily("gemini-2.5-pro", "openrouter"), "google");
|
|
402
|
+
assert.equal(getProviderFamily("some-model", "google"), "google");
|
|
403
|
+
assert.equal(getProviderFamily("gemini-2.0-flash", "vertex"), "google");
|
|
404
|
+
});
|
|
405
|
+
it("should fallback to other for unknown providers/models", () => {
|
|
406
|
+
assert.equal(getProviderFamily("meta-llama-3-8b", "together"), "other");
|
|
407
|
+
assert.equal(getProviderFamily("unknown", "unknown"), "other");
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
// ============================================================================
|
|
411
|
+
// calculateCost (provider-specific defaults verification)
|
|
412
|
+
// ============================================================================
|
|
413
|
+
describe("calculateCost", () => {
|
|
414
|
+
it("should apply Anthropic defaults correctly (cacheRead = 10%, cacheWrite = 125%)", () => {
|
|
415
|
+
// claude-sonnet-4 base: input = 3, output = 15.
|
|
416
|
+
// 1M inputs, 1M outputs, 100K cacheRead, 100K cacheWrite
|
|
417
|
+
const cost = calculateCost("claude-sonnet-4", "anthropic", 1_000_000, 1_000_000, 100_000, 100_000);
|
|
418
|
+
// Expected details:
|
|
419
|
+
// billable input = 1M - 100K = 900K tokens = 0.9 * 3.00 = $2.70
|
|
420
|
+
// output = 1M tokens = 1.0 * 15.00 = $15.00
|
|
421
|
+
// cacheRead = 100K tokens = 0.1 * 3.00 * 0.1 = $0.03 (10% discount rate)
|
|
422
|
+
// cacheWrite = 100K tokens = 0.1 * 3.00 * 1.25 = $0.375 (125% rate)
|
|
423
|
+
// total = 2.70 + 15.00 + 0.03 + 0.375 = $18.105
|
|
424
|
+
assert.ok(Math.abs(cost - 18.105) < 0.0001, `expected 18.105, got ${cost}`);
|
|
425
|
+
});
|
|
426
|
+
it("should apply OpenAI defaults correctly (cacheRead = 50%, cacheWrite = 0%)", () => {
|
|
427
|
+
// Built-in gpt-4o has input: 2.5, output: 10.
|
|
428
|
+
// 1M inputs, 1M outputs, 100K cacheRead, 100K cacheWrite
|
|
429
|
+
const cost = calculateCost("gpt-4o", "openai", 1_000_000, 1_000_000, 100_000, 100_000);
|
|
430
|
+
// Expected details:
|
|
431
|
+
// billable input = 1M - 100K = 900K tokens = 0.9 * 2.50 = $2.25
|
|
432
|
+
// output = 1M tokens = 1.0 * 10.00 = $10.00
|
|
433
|
+
// cacheRead = 100K tokens = 0.1 * 2.50 * 0.5 = $0.125 (50% discount rate)
|
|
434
|
+
// cacheWrite = 100K tokens = 0.1 * 2.50 * 0 = $0 (free)
|
|
435
|
+
// total = 2.25 + 10.00 + 0.125 + 0 = $12.375
|
|
436
|
+
assert.ok(Math.abs(cost - 12.375) < 0.0001, `expected 12.375, got ${cost}`);
|
|
437
|
+
});
|
|
438
|
+
it("should apply DeepSeek defaults correctly (cacheRead = 10%, cacheWrite = 0%)", () => {
|
|
439
|
+
// deepseek-chat has input: 0.28, output: 0.42. cacheRead: 0.028 (explicit in table)
|
|
440
|
+
const costExplicit = calculateCost("deepseek-chat", "deepseek", 1_000_000, 1_000_000, 100_000, 100_000);
|
|
441
|
+
// billable input = 900K = 0.9 * 0.28 = $0.252
|
|
442
|
+
// output = 1M = 1.0 * 0.42 = $0.42
|
|
443
|
+
// cacheRead = 100K = 0.1 * 0.028 = $0.0028
|
|
444
|
+
// cacheWrite = 100K = 0.1 * 0 = $0
|
|
445
|
+
// total = 0.252 + 0.42 + 0.0028 = $0.6748
|
|
446
|
+
assert.ok(Math.abs(costExplicit - 0.6748) < 0.0001, `expected 0.6748, got ${costExplicit}`);
|
|
447
|
+
// Verify fallback using non-builtin model under deepseek family:
|
|
448
|
+
const costFallback = calculateCost("deepseek-custom", "deepseek", 1_000_000, 1_000_000, 100_000, 100_000);
|
|
449
|
+
// Default base: input = 1, output = 4.
|
|
450
|
+
// billable input = 900K = 0.9 * 1.00 = $0.90
|
|
451
|
+
// output = 1M = 1.0 * 4.00 = $4.00
|
|
452
|
+
// cacheRead = 100K = 0.1 * 1.00 * 0.1 = $0.01 (10% rate fallback)
|
|
453
|
+
// cacheWrite = 100K = 0.1 * 1.00 * 0 = $0 (free cache write)
|
|
454
|
+
// total = 0.90 + 4.00 + 0.01 = $4.91
|
|
455
|
+
assert.ok(Math.abs(costFallback - 4.91) < 0.0001, `expected 4.91, got ${costFallback}`);
|
|
456
|
+
});
|
|
457
|
+
});
|
package/package.json
CHANGED