@ipv9/tokentracker-cli 0.39.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/LICENSE +21 -0
- package/README.ja.md +464 -0
- package/README.ko.md +464 -0
- package/README.md +476 -0
- package/README.zh-CN.md +469 -0
- package/bin/tracker.js +52 -0
- package/dashboard/dist/app-icon.png +0 -0
- package/dashboard/dist/apple-touch-icon.png +0 -0
- package/dashboard/dist/assets/ActivityHeatmap-BxBGHY1z.js +42 -0
- package/dashboard/dist/assets/Card-GROxUc_6.js +1 -0
- package/dashboard/dist/assets/DashboardPage-BZ5VFoBk.js +19 -0
- package/dashboard/dist/assets/DevicePage-CQts7ivB.js +1 -0
- package/dashboard/dist/assets/DialogTitle-DqFsk26U.js +1 -0
- package/dashboard/dist/assets/FadeIn-HBBXW_3q.js +1 -0
- package/dashboard/dist/assets/HeaderGithubStar-COD2HfaF.js +1 -0
- package/dashboard/dist/assets/IpCheckPage-Cn0l8-BQ.js +15 -0
- package/dashboard/dist/assets/LandingPage-CmV1BMU4.js +4356 -0
- package/dashboard/dist/assets/LeaderboardAvatar-CiIpWZwb.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-BU0mlR3g.js +6 -0
- package/dashboard/dist/assets/LeaderboardProfileModal-C49_3Xm9.js +72 -0
- package/dashboard/dist/assets/LeaderboardProfilePage-Bh1n0BAM.js +1 -0
- package/dashboard/dist/assets/LimitsPage-fZAmwLWT.js +2 -0
- package/dashboard/dist/assets/LocalOnlyNotice-D3rQdA-P.js +1 -0
- package/dashboard/dist/assets/LoginPage-enO4XP-c.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-pitJEpN1.js +1 -0
- package/dashboard/dist/assets/Select-tT9x4ntD.js +1 -0
- package/dashboard/dist/assets/SelectItemText-C7F7eIm0.js +1 -0
- package/dashboard/dist/assets/SettingsPage-pDasZNU2.js +1 -0
- package/dashboard/dist/assets/SkillsPage-DEu7FY-2.js +1 -0
- package/dashboard/dist/assets/WidgetsPage-DGcO7rbp.js +1 -0
- package/dashboard/dist/assets/WrappedPage-D_1ID213.js +1 -0
- package/dashboard/dist/assets/agent-logos-DXQVg1xy.js +1 -0
- package/dashboard/dist/assets/arrow-up-right-DNLo92Ba.js +1 -0
- package/dashboard/dist/assets/download-DbBBBpJF.js +1 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-BKC3PZkM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-DdrQ8KBw.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-CmoKXrdK.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-Cu5MFKsu.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-Br6WNUGb.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-CrmBTzU2.woff2 +0 -0
- package/dashboard/dist/assets/index-BeoRn2gJ.js +2 -0
- package/dashboard/dist/assets/info-Bh5qTz6k.js +1 -0
- package/dashboard/dist/assets/main-BIY3Nj6I.js +969 -0
- package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
- package/dashboard/dist/assets/use-limits-display-prefs-B4iJ-bBc.js +1 -0
- package/dashboard/dist/assets/use-native-settings-DHjPFDv8.js +1 -0
- package/dashboard/dist/assets/use-usage-limits-odqe9OY7.js +1 -0
- package/dashboard/dist/assets/useCurrency-BsSZiZMP.js +1 -0
- package/dashboard/dist/assets/useOpenInteractionType-BQhgq4rv.js +12 -0
- package/dashboard/dist/brand-logos/antigravity.svg +1 -0
- package/dashboard/dist/brand-logos/claude-code.svg +1 -0
- package/dashboard/dist/brand-logos/codebuddy.svg +1 -0
- package/dashboard/dist/brand-logos/codex.svg +15 -0
- package/dashboard/dist/brand-logos/copilot.svg +1 -0
- package/dashboard/dist/brand-logos/cursor.svg +1 -0
- package/dashboard/dist/brand-logos/gemini.svg +1 -0
- package/dashboard/dist/brand-logos/grok.svg +1 -0
- package/dashboard/dist/brand-logos/hermes.svg +1 -0
- package/dashboard/dist/brand-logos/kilo.svg +4 -0
- package/dashboard/dist/brand-logos/kimi.svg +1 -0
- package/dashboard/dist/brand-logos/kiro.svg +1 -0
- package/dashboard/dist/brand-logos/openclaw.svg +22 -0
- package/dashboard/dist/brand-logos/opencode.svg +3 -0
- package/dashboard/dist/clawd/happy.svg +161 -0
- package/dashboard/dist/clawd/idle/collapse.svg +101 -0
- package/dashboard/dist/clawd/idle/doze.svg +72 -0
- package/dashboard/dist/clawd/idle/follow.svg +64 -0
- package/dashboard/dist/clawd/idle/living.svg +196 -0
- package/dashboard/dist/clawd/idle/look.svg +115 -0
- package/dashboard/dist/clawd/idle/yawn.svg +158 -0
- package/dashboard/dist/clawd/mini/alert.svg +129 -0
- package/dashboard/dist/clawd/mini/crabwalk.svg +76 -0
- package/dashboard/dist/clawd/mini/enter-sleep.svg +65 -0
- package/dashboard/dist/clawd/mini/enter.svg +115 -0
- package/dashboard/dist/clawd/mini/happy.svg +124 -0
- package/dashboard/dist/clawd/mini/idle-tight.svg +15 -0
- package/dashboard/dist/clawd/mini/idle.svg +83 -0
- package/dashboard/dist/clawd/mini/peek.svg +82 -0
- package/dashboard/dist/clawd/mini/sleep.svg +112 -0
- package/dashboard/dist/clawd/react/double.svg +108 -0
- package/dashboard/dist/clawd/react/drag.svg +102 -0
- package/dashboard/dist/clawd/react/left.svg +102 -0
- package/dashboard/dist/clawd/react/right.svg +102 -0
- package/dashboard/dist/clawd/sleep/collapse-sleep.svg +247 -0
- package/dashboard/dist/clawd/sleep/sleeping.svg +118 -0
- package/dashboard/dist/clawd/sleep/wake.svg +277 -0
- package/dashboard/dist/clawd/static-base.svg +21 -0
- package/dashboard/dist/clawd/status/disconnected.svg +136 -0
- package/dashboard/dist/clawd/status/error.svg +94 -0
- package/dashboard/dist/clawd/status/notification.svg +134 -0
- package/dashboard/dist/clawd/working/building.svg +231 -0
- package/dashboard/dist/clawd/working/carrying.svg +107 -0
- package/dashboard/dist/clawd/working/conducting.svg +118 -0
- package/dashboard/dist/clawd/working/confused.svg +113 -0
- package/dashboard/dist/clawd/working/debugger.svg +91 -0
- package/dashboard/dist/clawd/working/juggling.svg +82 -0
- package/dashboard/dist/clawd/working/overheated.svg +75 -0
- package/dashboard/dist/clawd/working/pushing.svg +125 -0
- package/dashboard/dist/clawd/working/sweeping.svg +79 -0
- package/dashboard/dist/clawd/working/thinking.svg +119 -0
- package/dashboard/dist/clawd/working/typing.svg +153 -0
- package/dashboard/dist/clawd/working/ultrathink.svg +166 -0
- package/dashboard/dist/clawd/working/wizard.svg +98 -0
- package/dashboard/dist/dashboard-dark.png +0 -0
- package/dashboard/dist/favicon-16.png +0 -0
- package/dashboard/dist/favicon-32.png +0 -0
- package/dashboard/dist/favicon.ico +0 -0
- package/dashboard/dist/feed.xml +34 -0
- package/dashboard/dist/icon-192.png +0 -0
- package/dashboard/dist/icon-512.png +0 -0
- package/dashboard/dist/index.html +355 -0
- package/dashboard/dist/llms.txt +53 -0
- package/dashboard/dist/neon_bg.png +0 -0
- package/dashboard/dist/robots.txt +7 -0
- package/dashboard/dist/share.html +140 -0
- package/dashboard/dist/sitemap.xml +27 -0
- package/dashboard/dist/widgets-overview.png +0 -0
- package/package.json +78 -0
- package/scripts/install-local-service.sh +140 -0
- package/scripts/uninstall-local-service.sh +21 -0
- package/src/cli.js +95 -0
- package/src/commands/device-login.js +161 -0
- package/src/commands/diagnostics.js +38 -0
- package/src/commands/doctor.js +96 -0
- package/src/commands/init.js +1195 -0
- package/src/commands/serve.js +351 -0
- package/src/commands/status.js +609 -0
- package/src/commands/sync.js +2285 -0
- package/src/commands/uninstall.js +189 -0
- package/src/commands/wrapped.js +150 -0
- package/src/lib/antigravity-paths.js +48 -0
- package/src/lib/browser-auth.js +281 -0
- package/src/lib/categorizer-utils.js +232 -0
- package/src/lib/claude-categorizer.js +1319 -0
- package/src/lib/claude-config.js +204 -0
- package/src/lib/cli-ui.js +179 -0
- package/src/lib/codex-config.js +350 -0
- package/src/lib/codex-context-breakdown.js +400 -0
- package/src/lib/codex-rollout-parser.js +623 -0
- package/src/lib/codex-token-refresh.js +112 -0
- package/src/lib/cursor-config.js +425 -0
- package/src/lib/debug-flags.js +7 -0
- package/src/lib/diagnostics.js +272 -0
- package/src/lib/doctor.js +338 -0
- package/src/lib/fs.js +91 -0
- package/src/lib/gemini-config.js +288 -0
- package/src/lib/grok-hook.js +351 -0
- package/src/lib/init-flow.js +42 -0
- package/src/lib/local-api.js +1861 -0
- package/src/lib/openclaw-hook.js +420 -0
- package/src/lib/openclaw-session-plugin.js +520 -0
- package/src/lib/opencode-config.js +99 -0
- package/src/lib/passive-mode.js +185 -0
- package/src/lib/pricing/curated-overrides.json +79 -0
- package/src/lib/pricing/index.js +143 -0
- package/src/lib/pricing/litellm-fetcher.js +172 -0
- package/src/lib/pricing/matcher.js +255 -0
- package/src/lib/pricing/seed-snapshot.json +1 -0
- package/src/lib/progress.js +76 -0
- package/src/lib/project-usage-purge.js +106 -0
- package/src/lib/prompt.js +24 -0
- package/src/lib/proxy-env.js +131 -0
- package/src/lib/rollout.js +8442 -0
- package/src/lib/runtime-config.js +122 -0
- package/src/lib/skill-usage.js +267 -0
- package/src/lib/skills-manager.js +1127 -0
- package/src/lib/source-metadata.js +48 -0
- package/src/lib/sqlite-reader.js +136 -0
- package/src/lib/static-server.js +59 -0
- package/src/lib/subscriptions.js +453 -0
- package/src/lib/tracker-paths.js +16 -0
- package/src/lib/upload-throttle.js +148 -0
- package/src/lib/usage-limits.js +1806 -0
- package/src/lib/wrapped-aggregator.js +225 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Pure pricing-lookup logic. No I/O, no async. Tested in isolation.
|
|
2
|
+
//
|
|
3
|
+
// Resolve order:
|
|
4
|
+
// 1. CURATED exact match (self-defined aliases like kiro-*, hy3-*)
|
|
5
|
+
// 2. LiteLLM exact match (mainstream claude/gpt-5/gemini)
|
|
6
|
+
// 3. CURATED alias (e.g. "auto" -> "composer-1")
|
|
7
|
+
// 4. CURATED fuzzy substring (e.g. "kiro-future-xyz" matches via "kiro")
|
|
8
|
+
// 5. LiteLLM suffix-strip (gpt-5-codex-high-fast -> gpt-5-codex)
|
|
9
|
+
// 5b. LiteLLM provider-prefix strip (mimo-v2.5-pro -> openrouter/xiaomi/mimo-v2.5-pro)
|
|
10
|
+
// 6. LiteLLM reverse substring (longest-key first)
|
|
11
|
+
// 7. null (caller decides what to do — typically zero-pricing + negative cache)
|
|
12
|
+
|
|
13
|
+
const SUFFIX_STRIP_PATTERNS = [
|
|
14
|
+
/-xhigh-fast$/,
|
|
15
|
+
/-high-fast$/,
|
|
16
|
+
/-medium-fast$/,
|
|
17
|
+
/-low-fast$/,
|
|
18
|
+
/-xhigh$/,
|
|
19
|
+
/-high$/,
|
|
20
|
+
/-medium$/,
|
|
21
|
+
/-low$/,
|
|
22
|
+
/-fast$/,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function stripReasoningSuffix(model) {
|
|
26
|
+
for (const re of SUFFIX_STRIP_PATTERNS) {
|
|
27
|
+
if (re.test(model)) return model.replace(re, "");
|
|
28
|
+
}
|
|
29
|
+
return model;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeAntigravityModel(model) {
|
|
33
|
+
if (!model || typeof model !== "string") return model;
|
|
34
|
+
let lower = model
|
|
35
|
+
.trim()
|
|
36
|
+
.replace(/\([^)]*\)/g, " ")
|
|
37
|
+
.replace(/\b(thinking|xhigh|high|medium|low|fast)\b/gi, " ")
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.replace(/[^a-z0-9.]+/g, "-")
|
|
40
|
+
.replace(/^-+|-+$/g, "")
|
|
41
|
+
.replace(/-{2,}/g, "-");
|
|
42
|
+
lower = stripReasoningSuffix(lower);
|
|
43
|
+
|
|
44
|
+
if (lower.startsWith("gemini-claude-") || lower.startsWith("gemini-gpt-")) {
|
|
45
|
+
lower = lower.substring(7);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (/^gemini-3\.\d+-flash-lite/.test(lower)) return "gemini-2.5-flash-lite";
|
|
49
|
+
if (/^gemini-3\.\d+-flash/.test(lower)) return "gemini-2.5-flash";
|
|
50
|
+
if (/^gemini-3\.\d+-pro/.test(lower)) return "gemini-2.5-pro";
|
|
51
|
+
if (/^claude-(sonnet|opus|haiku)-4\.\d+/.test(lower)) {
|
|
52
|
+
return lower.replace(/^claude-(sonnet|opus|haiku)-4\.(\d+)/, "claude-$1-4-$2");
|
|
53
|
+
}
|
|
54
|
+
if (lower.startsWith("gpt-oss-120b")) return "antigravity-gpt-oss-120b";
|
|
55
|
+
|
|
56
|
+
return lower;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function shouldNormalizeAntigravity(source) {
|
|
60
|
+
return typeof source === "string" && source.toLowerCase() === "antigravity";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Memoise the sorted-by-length LiteLLM key list. Reverse-substring scan walks
|
|
64
|
+
// this once per uncached model; ~2k keys × negligible per-iteration cost, but
|
|
65
|
+
// computing the sort on every call would add up across a sync.
|
|
66
|
+
const sortedKeysCache = new WeakMap();
|
|
67
|
+
function getSortedKeys(litellm) {
|
|
68
|
+
let cached = sortedKeysCache.get(litellm);
|
|
69
|
+
if (!cached) {
|
|
70
|
+
cached = Object.keys(litellm).sort((a, b) => b.length - a.length);
|
|
71
|
+
sortedKeysCache.set(litellm, cached);
|
|
72
|
+
}
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildDotRestoredModel(model) {
|
|
77
|
+
if (typeof model !== "string") return "";
|
|
78
|
+
const lower = model.toLowerCase();
|
|
79
|
+
const restored = lower.replace(/(\d+)-(\d+)/g, "$1.$2");
|
|
80
|
+
return restored === lower ? "" : restored;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function lookupExactCaseInsensitive(table, model) {
|
|
84
|
+
if (!table || !model) return null;
|
|
85
|
+
if (table[model]) return table[model];
|
|
86
|
+
const lower = model.toLowerCase();
|
|
87
|
+
for (const key of Object.keys(table)) {
|
|
88
|
+
if (key.toLowerCase() === lower) return table[key];
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function lookupContainedExactCaseInsensitive(table, model) {
|
|
94
|
+
if (!table || !model) return null;
|
|
95
|
+
const lower = model.toLowerCase();
|
|
96
|
+
const keys = Object.keys(table).sort((a, b) => b.length - a.length);
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
if (lower.includes(key.toLowerCase())) return table[key];
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
104
|
+
if (!model || typeof model !== "string") {
|
|
105
|
+
return { hit: false, source: "empty", value: null };
|
|
106
|
+
}
|
|
107
|
+
const lookupModel = shouldNormalizeAntigravity(source)
|
|
108
|
+
? normalizeAntigravityModel(model)
|
|
109
|
+
: model;
|
|
110
|
+
const lower = lookupModel.toLowerCase();
|
|
111
|
+
const dotForm = buildDotRestoredModel(lookupModel);
|
|
112
|
+
|
|
113
|
+
// 1. CURATED exact
|
|
114
|
+
if (curated.exact && curated.exact[lookupModel]) {
|
|
115
|
+
return { hit: true, source: "curated:exact", value: curated.exact[lookupModel] };
|
|
116
|
+
}
|
|
117
|
+
const curatedDotExact = lookupExactCaseInsensitive(curated.exact, dotForm);
|
|
118
|
+
if (curatedDotExact) {
|
|
119
|
+
return { hit: true, source: "curated:exact-dot", value: curatedDotExact };
|
|
120
|
+
}
|
|
121
|
+
const curatedDotContainedExact = lookupContainedExactCaseInsensitive(curated.exact, dotForm);
|
|
122
|
+
if (curatedDotContainedExact) {
|
|
123
|
+
return { hit: true, source: "curated:exact-dot", value: curatedDotContainedExact };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. LiteLLM exact
|
|
127
|
+
if (litellm && litellm[lookupModel]) {
|
|
128
|
+
return { hit: true, source: "litellm:exact", value: litellm[lookupModel] };
|
|
129
|
+
}
|
|
130
|
+
const litellmDotExact = lookupExactCaseInsensitive(litellm, dotForm);
|
|
131
|
+
if (litellmDotExact) {
|
|
132
|
+
return { hit: true, source: "litellm:exact-dot", value: litellmDotExact };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 3. CURATED alias (literal mapping like "auto" -> "composer-1")
|
|
136
|
+
if (curated.alias && curated.alias[lookupModel] && curated.exact[curated.alias[lookupModel]]) {
|
|
137
|
+
return {
|
|
138
|
+
hit: true,
|
|
139
|
+
source: "curated:alias",
|
|
140
|
+
value: curated.exact[curated.alias[lookupModel]],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 4. CURATED fuzzy substring. Also try a dot-restored variant of the input
|
|
145
|
+
// (digits separated by `-` rejoined as `.`) so providers that dash-normalize
|
|
146
|
+
// numeric segments — Droid emits `glm-5-1-0` for upstream `GLM-5.1` — still
|
|
147
|
+
// resolve against dot-keyed curated entries like `glm-5.1`, `glm-4.6`, etc.
|
|
148
|
+
// The regex only fires on digit-dash-digit, so `claude-3-7-sonnet`,
|
|
149
|
+
// `gpt-5-codex`, `gemini-2-5-pro` are unaffected (no digit-pair to rejoin or
|
|
150
|
+
// no matching curated key).
|
|
151
|
+
if (Array.isArray(curated.fuzzy)) {
|
|
152
|
+
for (const { match, ref } of curated.fuzzy) {
|
|
153
|
+
if (!match || !ref) continue;
|
|
154
|
+
const needle = match.toLowerCase();
|
|
155
|
+
if (!curated.exact[ref]) continue;
|
|
156
|
+
if (lower.includes(needle)) {
|
|
157
|
+
return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
|
|
158
|
+
}
|
|
159
|
+
if (dotForm && dotForm.includes(needle)) {
|
|
160
|
+
return { hit: true, source: "curated:fuzzy", value: curated.exact[ref] };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 5. LiteLLM suffix-strip
|
|
166
|
+
if (litellm) {
|
|
167
|
+
const stripped = stripReasoningSuffix(lookupModel);
|
|
168
|
+
if (stripped !== lookupModel && litellm[stripped]) {
|
|
169
|
+
return { hit: true, source: "litellm:strip", value: litellm[stripped] };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 5b. LiteLLM provider-prefix strip. Queue rows store the bare model name
|
|
174
|
+
// (e.g. "mimo-v2.5-pro"), but LiteLLM keys are provider-qualified (e.g.
|
|
175
|
+
// "openrouter/xiaomi/mimo-v2.5-pro"), so the exact lookups above miss. Match
|
|
176
|
+
// any key whose path suffix equals the bare model. When several providers
|
|
177
|
+
// expose the same model, pick the lexicographically smallest key so the
|
|
178
|
+
// resolved price is deterministic and independent of JSON ordering. Runs
|
|
179
|
+
// AFTER curated alias/fuzzy so e.g. Cursor's "auto" still resolves to
|
|
180
|
+
// composer-1 rather than a LiteLLM "*/auto" entry.
|
|
181
|
+
if (litellm) {
|
|
182
|
+
const suffix = "/" + lower;
|
|
183
|
+
let best = null;
|
|
184
|
+
for (const key of Object.keys(litellm)) {
|
|
185
|
+
if (key.length > suffix.length && key.toLowerCase().endsWith(suffix)) {
|
|
186
|
+
if (best === null || key < best) best = key;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (best) return { hit: true, source: "litellm:prefix-strip", value: litellm[best] };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 6. LiteLLM reverse substring (longest-key first)
|
|
193
|
+
if (litellm) {
|
|
194
|
+
const sorted = getSortedKeys(litellm);
|
|
195
|
+
for (const key of sorted) {
|
|
196
|
+
const keyLower = key.toLowerCase();
|
|
197
|
+
// Only accept if model is a superset of key (model contains key), to
|
|
198
|
+
// avoid e.g. "gpt-5" matching "gpt-5-pro" in the wrong direction.
|
|
199
|
+
if (lower.includes(keyLower) || (dotForm && dotForm.includes(keyLower))) {
|
|
200
|
+
return { hit: true, source: "litellm:fuzzy", value: litellm[key] };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { hit: false, source: "miss", value: null };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Convert one LiteLLM entry (per-token) to internal per-million USD shape.
|
|
209
|
+
// Missing fields stay missing — callers default with `(pricing.x || 0)`.
|
|
210
|
+
//
|
|
211
|
+
// Why the round: floating-point math means 1e-7 * 1e6 = 0.09999999999999999.
|
|
212
|
+
// Rounding to 10 significant decimals ($0.0000000001 / MTok) is well below
|
|
213
|
+
// any realistic price step but cleans up the printed/asserted numbers.
|
|
214
|
+
function roundToTenDecimals(n) {
|
|
215
|
+
return Math.round(n * 1e10) / 1e10;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function convertLitellmEntry(entry) {
|
|
219
|
+
if (!entry || typeof entry !== "object") return null;
|
|
220
|
+
const out = {};
|
|
221
|
+
if (typeof entry.input_cost_per_token === "number") {
|
|
222
|
+
out.input = roundToTenDecimals(entry.input_cost_per_token * 1_000_000);
|
|
223
|
+
}
|
|
224
|
+
if (typeof entry.output_cost_per_token === "number") {
|
|
225
|
+
out.output = roundToTenDecimals(entry.output_cost_per_token * 1_000_000);
|
|
226
|
+
}
|
|
227
|
+
if (typeof entry.cache_read_input_token_cost === "number") {
|
|
228
|
+
out.cache_read = roundToTenDecimals(entry.cache_read_input_token_cost * 1_000_000);
|
|
229
|
+
}
|
|
230
|
+
if (typeof entry.cache_creation_input_token_cost === "number") {
|
|
231
|
+
out.cache_write = roundToTenDecimals(entry.cache_creation_input_token_cost * 1_000_000);
|
|
232
|
+
}
|
|
233
|
+
return Object.keys(out).length ? out : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build a per-million-USD map from a LiteLLM raw map (or seed snapshot which
|
|
237
|
+
// uses the same field names). Skips meta keys starting with "_".
|
|
238
|
+
function buildLitellmPerMillionMap(rawData) {
|
|
239
|
+
if (!rawData || typeof rawData !== "object") return {};
|
|
240
|
+
const out = {};
|
|
241
|
+
for (const [name, entry] of Object.entries(rawData)) {
|
|
242
|
+
if (name.startsWith("_")) continue;
|
|
243
|
+
const converted = convertLitellmEntry(entry);
|
|
244
|
+
if (converted) out[name] = converted;
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
lookupPricing,
|
|
251
|
+
stripReasoningSuffix,
|
|
252
|
+
normalizeAntigravityModel,
|
|
253
|
+
convertLitellmEntry,
|
|
254
|
+
buildLitellmPerMillionMap,
|
|
255
|
+
};
|