tokentracker-cli 0.97.1 → 0.98.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.de.md +3 -3
- package/README.ja.md +3 -3
- package/README.ko.md +3 -3
- package/README.md +7 -5
- package/README.zh-CN.md +3 -3
- package/dashboard/dist/assets/AchievementBadge-DlZklawB.js +1 -0
- package/dashboard/dist/assets/{AchievementsPage-CrqvRxnb.js → AchievementsPage-CprbOKnt.js} +2 -2
- package/dashboard/dist/assets/AchievementsSection-BKLJBRKw.js +1 -0
- package/dashboard/dist/assets/{BadgeDetailModal-CquST3dj.js → BadgeDetailModal-CfVXLh_t.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-EXhdUHSg.js → BotAnimated-wzqeGbEY.js} +1 -1
- package/dashboard/dist/assets/{Card-Il2WWgOG.js → Card-B-gcOAn7.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-DioF5wa-.js → ClawdAnimated-1c5ezBBN.js} +2 -2
- package/dashboard/dist/assets/CommandPalette-CD8ggEF6.js +1 -0
- package/dashboard/dist/assets/CommunityStatsModal-b2fPhi1E.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-BHWQLUCM.js → ConfirmModal-ARBkT8AC.js} +1 -1
- package/dashboard/dist/assets/{Controls--LFhExZX.js → Controls-WFXDjIYG.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-XtiZa2JN.js +1 -0
- package/dashboard/dist/assets/{DevicePage-RKTirqz0.js → DevicePage-BCwSpNXa.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-DPFsTKmJ.js → DialogClose-D2PE0TAc.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-5LunWukT.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DolLpmFL.js → DialogTitle-Bby0zSCw.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-92GGNCPe.js → FadeIn-Cx1hl4so.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-GfXudotT.js → HeaderGithubStar-my4zR6Pp.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-n7gbPEsq.js → HoverTooltip-DOiGAPLE.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BaQOTbXi.js → IpCheckPage-4SdHlFuA.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-D3JcvxW_.js → LandingPage-bSWc9xiS.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-tVaWJXZ3.js → LeaderboardAvatar-EN4117E5.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-Bwjf-xZl.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-DmvcCVS7.js → LeaderboardProfileModal-B1mgVtfP.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-VSv3IlzS.js → LeaderboardProfilePage-DVt5S3lY.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-CtxgT20z.js → LimitsPage-BCXjJE1B.js} +5 -5
- package/dashboard/dist/assets/{LocalOnlyNotice-O9o586G8.js → LocalOnlyNotice-CzeH4OoJ.js} +1 -1
- package/dashboard/dist/assets/LoginCard-aiqSQInP.js +1 -0
- package/dashboard/dist/assets/{LoginModal-CJ3g7tBc.js → LoginModal-DdGKIi8h.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DPWzPywx.js → LoginPage-D7xGSoMn.js} +1 -1
- package/dashboard/dist/assets/PetPage-Dlb6rNpF.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-nL3YOLPO.js → PopoverPopup-Xbruj7Zi.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-C5RX_xPk.js → ProviderIcon-BPxacBzj.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DW_NCwA_.js → ResetPasswordPage-9wgPnLRj.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CS6wQtck.js → SegmentedControl-CYFcZFNE.js} +1 -1
- package/dashboard/dist/assets/{Select-YqlqvbT5.js → Select-zpFEMqpk.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BahqsiIc.js → SelectItemText-Bldm5Y0C.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-DyhutLUy.js → ServiceStatusPage-dgtwFazV.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-wbvi4EHi.js → SessionsPage-DHfpv53M.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-DcGToOT7.js +2 -0
- package/dashboard/dist/assets/SkillsPage-DiQDLw2l.js +1 -0
- package/dashboard/dist/assets/TokenGalaxy-GDx7ftMU.js +4426 -0
- package/dashboard/dist/assets/ToolbarRootContext-PoCcP2AV.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-BtBcCjx-.js → TrendMonitor-CIfq_yk8.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BSNl2WIL.js → WidgetsPage-Dwy-E3Lf.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-d5oiNV4T.js → WrappedPage-Ce7NV6Ij.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-D-WLjvZW.js +45 -0
- package/dashboard/dist/assets/{agent-logos-CCVzO3uU.js → agent-logos-BY8n7b9g.js} +1 -1
- package/dashboard/dist/assets/arrow-up-right-BDiV6Wlk.js +1 -0
- package/dashboard/dist/assets/check-Chyo6ujc.js +1 -0
- package/dashboard/dist/assets/{composite-CWz1s64R.js → composite-DgBqMs_Z.js} +1 -1
- package/dashboard/dist/assets/copy-ENRv4pAy.js +1 -0
- package/dashboard/dist/assets/download-D3y3gd-z.js +1 -0
- package/dashboard/dist/assets/external-link-VxSAxWnH.js +1 -0
- package/dashboard/dist/assets/flame-CPdTWeRo.js +1 -0
- package/dashboard/dist/assets/{format-tokens-Bs2YotMr.js → format-tokens-BF2G89Q7.js} +1 -1
- package/dashboard/dist/assets/{icons-OKyij2y_.js → icons-D6rfdVyV.js} +1 -1
- package/dashboard/dist/assets/{index-CN7xY7j_.js → index-4bHcp2Ep.js} +1 -1
- package/dashboard/dist/assets/{index-C8nY1OaT.js → index-CgmpxGym.js} +1 -1
- package/dashboard/dist/assets/{index-DOwPCQx2.js → index-mzMCUT_4.js} +1 -1
- package/dashboard/dist/assets/info-BMso9PcY.js +1 -0
- package/dashboard/dist/assets/link-2-Bg34XOcd.js +1 -0
- package/dashboard/dist/assets/loader-circle-C4Vul1Yx.js +1 -0
- package/dashboard/dist/assets/maximize-2-BBqcwFFo.js +1 -0
- package/dashboard/dist/assets/{modifiers.esm-D_aVVjx5.js → modifiers.esm-B2F4Iiey.js} +1 -1
- package/dashboard/dist/assets/module-B5tW98jG.js +3 -0
- package/dashboard/dist/assets/monitor-smartphone-k1Ja3ngF.js +1 -0
- package/dashboard/dist/assets/plus-BaCIejxc.js +1 -0
- package/dashboard/dist/assets/provider-display-BYxmO9Bu.js +42 -0
- package/dashboard/dist/assets/{react-jS0-5kwE.js → react-xGx5RIqz.js} +1 -1
- package/dashboard/dist/assets/search-LZQC7j6a.js +1 -0
- package/dashboard/dist/assets/{skills-api-PwUMTdmV.js → skills-api-yLfQkApo.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BDdw7tkz.js → sortable.esm-B8XRHrmA.js} +1 -1
- package/dashboard/dist/assets/{store-CFdu9yv_.js → store-DfUIKvz3.js} +1 -1
- package/dashboard/dist/assets/terminal-CsFh6eZS.js +1 -0
- package/dashboard/dist/assets/trash-CVk10mDa.js +1 -0
- package/dashboard/dist/assets/{use-community-stats-D55oU9bu.js → use-community-stats-DP7lJyDV.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BO89yQ3n.js → use-latest-request-guard-B7uLArxV.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-C358N7B0.js → use-limits-display-prefs-DG97cWcv.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-B9ZYDS1K.js → use-ordered-list-ChQc2v-Q.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BoFF1l9U.js +1 -0
- package/dashboard/dist/assets/use-session-efficiency-pref-CtF9JAlF.js +1 -0
- package/dashboard/dist/assets/{use-transform-9S4Ruq_s.js → use-transform-DVIBk7FO.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CpnM5V06.js → use-usage-limits-BYQuJ9Ud.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BKJGW_4l.js → useCurrency-CNkzJCP9.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-CTB5svOB.js → useTokenFormat-CU3MtXtS.js} +1 -1
- package/dashboard/dist/assets/zap-D5k595AR.js +1 -0
- package/dashboard/dist/index.html +13 -11
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +3 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +1 -0
- package/src/commands/status.js +13 -0
- package/src/commands/sync.js +61 -4
- package/src/lib/cloud-account.js +108 -0
- package/src/lib/grok-limits.js +37 -0
- package/src/lib/opencode-config.js +20 -1
- package/src/lib/pricing/curated-overrides.json +22 -0
- package/src/lib/pricing/index.js +9 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1244 -168
- package/src/lib/usage-limits.js +7 -1
- package/dashboard/dist/assets/AchievementBadge-BdvqY4Qb.js +0 -1
- package/dashboard/dist/assets/AchievementsSection-wyLAM9N7.js +0 -1
- package/dashboard/dist/assets/CommandPalette-CaDx06tM.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-BbYWzM07.js +0 -1
- package/dashboard/dist/assets/DashboardPage-DuqJOpHL.js +0 -1
- package/dashboard/dist/assets/DialogDescription-DgaV8Gtk.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-NqHcStnk.js +0 -2
- package/dashboard/dist/assets/LoginCard-JCejigVu.js +0 -1
- package/dashboard/dist/assets/PetPage-MSJ3jseK.js +0 -1
- package/dashboard/dist/assets/SettingsPage-HGSwpV1O.js +0 -2
- package/dashboard/dist/assets/SkillsPage-BbVT9oyh.js +0 -1
- package/dashboard/dist/assets/TokenGalaxy-Cej6MLu6.js +0 -4314
- package/dashboard/dist/assets/ToolbarRootContext-D9pPY73U.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-CjrP5KfC.js +0 -45
- package/dashboard/dist/assets/arrow-up-right-AKItXMyh.js +0 -1
- package/dashboard/dist/assets/check-BQw5n0V0.js +0 -1
- package/dashboard/dist/assets/copy-CF8XNmv3.js +0 -1
- package/dashboard/dist/assets/download-BZvLU_hD.js +0 -1
- package/dashboard/dist/assets/external-link-bZrcVkeE.js +0 -1
- package/dashboard/dist/assets/flame-CMHolW1z.js +0 -1
- package/dashboard/dist/assets/info-CU7bMBhU.js +0 -1
- package/dashboard/dist/assets/link-2-wwoUOujz.js +0 -1
- package/dashboard/dist/assets/loader-circle-BK3Ltc-j.js +0 -1
- package/dashboard/dist/assets/maximize-2-DIPnLqR8.js +0 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +0 -3
- package/dashboard/dist/assets/monitor-smartphone-4NOM9BHb.js +0 -1
- package/dashboard/dist/assets/plus-JqSTJ28Y.js +0 -1
- package/dashboard/dist/assets/provider-display-CEFlg9ho.js +0 -42
- package/dashboard/dist/assets/search-CbBetPN7.js +0 -1
- package/dashboard/dist/assets/terminal-Dh7Sh6IG.js +0 -1
- package/dashboard/dist/assets/trash-B6_OaEVL.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DWHVI51t.js +0 -1
- package/dashboard/dist/assets/use-session-efficiency-pref-CwGRDyVq.js +0 -1
- package/dashboard/dist/assets/zap-DGdBuT6m.js +0 -1
package/src/lib/cloud-account.js
CHANGED
|
@@ -68,6 +68,23 @@ function decodeJwtExpMs(token) {
|
|
|
68
68
|
return 0;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// The account id the access token was minted for. Used to key the payload cache
|
|
72
|
+
// below on something the backend does NOT rotate: the refresh token changes
|
|
73
|
+
// under us on rotation, which would throw away a still-warm cache entry on
|
|
74
|
+
// exactly the reads that repeat most.
|
|
75
|
+
function decodeJwtSub(token) {
|
|
76
|
+
try {
|
|
77
|
+
const part = String(token || "").split(".")[1];
|
|
78
|
+
if (!part) return "";
|
|
79
|
+
const json = Buffer.from(part.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
80
|
+
const payload = JSON.parse(json);
|
|
81
|
+
if (payload && typeof payload.sub === "string") return payload.sub;
|
|
82
|
+
} catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
// Module-level access-token cache, keyed by the cloud base URL and refresh token
|
|
72
89
|
// that produced it.
|
|
73
90
|
// The popover polls frequently, so caching avoids hammering /api/auth/refresh.
|
|
@@ -81,9 +98,78 @@ let tokenCache = { cacheKey: null, accessToken: null, expMs: 0 };
|
|
|
81
98
|
// intermittent "Activity silently dropped to this-machine data" symptom.
|
|
82
99
|
const mintInflight = new Map();
|
|
83
100
|
|
|
101
|
+
// Response cache for the account reads, mirroring the 30-second window the edge
|
|
102
|
+
// functions already keep.
|
|
103
|
+
//
|
|
104
|
+
// Each `tokentracker-account-*` function caches the RPC snapshot it renders
|
|
105
|
+
// from for 30 seconds (`COMPACT_TTL_MS` in dashboard/edge-patches/*), so two
|
|
106
|
+
// reads inside one window render the same numbers from the same snapshot. That
|
|
107
|
+
// cache only saves the edge-to-PostgREST hop, though: the full body still goes
|
|
108
|
+
// out to every caller. And the callers repeat a lot — the Windows tray poller
|
|
109
|
+
// re-reads the 52-week heatmap every ~49 seconds, and one popover refresh fans
|
|
110
|
+
// out six reads at once. Holding the payload here for the same 30 seconds
|
|
111
|
+
// returns exactly what the cloud would have returned, without paying for the
|
|
112
|
+
// transfer again.
|
|
113
|
+
//
|
|
114
|
+
// Bodies are stored serialized and re-parsed on a hit so a caller mutating the
|
|
115
|
+
// object it got back cannot corrupt what the next caller reads.
|
|
116
|
+
const PAYLOAD_TTL_MS = 30_000;
|
|
117
|
+
const PAYLOAD_CACHE_MAX = 64;
|
|
118
|
+
const payloadCache = new Map();
|
|
119
|
+
|
|
120
|
+
// Mirrors the params `fetchAccountFunction` actually puts on the wire, so two
|
|
121
|
+
// requests share an entry only when they would have produced the same URL.
|
|
122
|
+
function payloadCacheKey({ root, sub, slug, searchParams }) {
|
|
123
|
+
const pairs = [];
|
|
124
|
+
if (searchParams && typeof searchParams.entries === "function") {
|
|
125
|
+
for (const [key, value] of searchParams.entries()) {
|
|
126
|
+
if (key === "account" || key === "scope") continue;
|
|
127
|
+
if (value == null || value === "") continue;
|
|
128
|
+
pairs.push(`${key}=${String(value)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
pairs.sort();
|
|
132
|
+
return `${root}\0${sub}\0${slug}\0${pairs.join("&")}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function payloadCacheGet(key, now) {
|
|
136
|
+
const hit = payloadCache.get(key);
|
|
137
|
+
if (!hit) return null;
|
|
138
|
+
if (hit.expMs <= now()) {
|
|
139
|
+
payloadCache.delete(key);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Re-insert so the eviction below drops the coldest entry, not this one.
|
|
143
|
+
payloadCache.delete(key);
|
|
144
|
+
payloadCache.set(key, hit);
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(hit.body);
|
|
147
|
+
} catch {
|
|
148
|
+
payloadCache.delete(key);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function payloadCacheSet(key, data, now) {
|
|
154
|
+
let body;
|
|
155
|
+
try {
|
|
156
|
+
body = JSON.stringify(data);
|
|
157
|
+
} catch {
|
|
158
|
+
return; // Not serializable: skip the cache rather than fail the read.
|
|
159
|
+
}
|
|
160
|
+
if (body === undefined) return;
|
|
161
|
+
payloadCache.set(key, { expMs: now() + PAYLOAD_TTL_MS, body });
|
|
162
|
+
while (payloadCache.size > PAYLOAD_CACHE_MAX) {
|
|
163
|
+
const oldest = payloadCache.keys().next();
|
|
164
|
+
if (oldest.done) break;
|
|
165
|
+
payloadCache.delete(oldest.value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
84
169
|
function __resetCloudAccountCacheForTests() {
|
|
85
170
|
tokenCache = { cacheKey: null, accessToken: null, expMs: 0 };
|
|
86
171
|
mintInflight.clear();
|
|
172
|
+
payloadCache.clear();
|
|
87
173
|
}
|
|
88
174
|
|
|
89
175
|
// Failure classes surfaced to local-api so it can tell an intentional local
|
|
@@ -328,6 +414,25 @@ async function fetchAccountUsage({
|
|
|
328
414
|
});
|
|
329
415
|
if (!minted) return null;
|
|
330
416
|
|
|
417
|
+
// Keyed after the mint so rotation cannot invalidate the entry, and so a
|
|
418
|
+
// revoked session fails here rather than being served from cache.
|
|
419
|
+
const sub = decodeJwtSub(minted.accessToken);
|
|
420
|
+
const cacheKey = sub
|
|
421
|
+
? payloadCacheKey({ root: String(baseUrl || DEFAULT_BASE_URL).replace(/\/$/, ""), sub, slug, searchParams })
|
|
422
|
+
: null;
|
|
423
|
+
if (cacheKey) {
|
|
424
|
+
const cached = payloadCacheGet(cacheKey, now);
|
|
425
|
+
if (cached !== null) {
|
|
426
|
+
// Still surface a rotation from this mint: dropping it would leave the
|
|
427
|
+
// caller relaying a refresh token the backend has already consumed.
|
|
428
|
+
return {
|
|
429
|
+
data: cached,
|
|
430
|
+
rotatedRefreshToken: minted.refreshToken,
|
|
431
|
+
rotatedCsrfToken: minted.csrfToken,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
331
436
|
const data = await fetchAccountFunction({
|
|
332
437
|
baseUrl,
|
|
333
438
|
anonKey,
|
|
@@ -337,16 +442,19 @@ async function fetchAccountUsage({
|
|
|
337
442
|
fetchImpl,
|
|
338
443
|
timeoutMs: getRemainingTimeout(),
|
|
339
444
|
});
|
|
445
|
+
if (cacheKey && data != null) payloadCacheSet(cacheKey, data, now);
|
|
340
446
|
return { data, rotatedRefreshToken: minted.refreshToken, rotatedCsrfToken: minted.csrfToken };
|
|
341
447
|
}
|
|
342
448
|
|
|
343
449
|
module.exports = {
|
|
344
450
|
AccountAuthError,
|
|
345
451
|
USAGE_TO_ACCOUNT_SLUG,
|
|
452
|
+
PAYLOAD_TTL_MS,
|
|
346
453
|
accountSlugFor,
|
|
347
454
|
accessTokenFromRefreshPayload,
|
|
348
455
|
refreshTokenFromRefreshPayload,
|
|
349
456
|
decodeJwtExpMs,
|
|
457
|
+
decodeJwtSub,
|
|
350
458
|
mintAccessToken,
|
|
351
459
|
fetchAccountFunction,
|
|
352
460
|
fetchAccountUsage,
|
package/src/lib/grok-limits.js
CHANGED
|
@@ -495,6 +495,40 @@ async function resolveGrokAccessToken({
|
|
|
495
495
|
};
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
// xAI reports the subscription as a human-readable product name on the billing
|
|
499
|
+
// payload's top level (`subscriptionTier`, a sibling of `config`, not a member
|
|
500
|
+
// of it) -- a Free account reads exactly "Free".
|
|
501
|
+
const GROK_PLAN_TIERS = new Map([
|
|
502
|
+
["supergrokheavy", "SuperGrok Heavy"],
|
|
503
|
+
["supergrokplus", "SuperGrok Plus"],
|
|
504
|
+
["supergroklite", "SuperGrok Lite"],
|
|
505
|
+
["supergrok", "SuperGrok"],
|
|
506
|
+
["xpremiumplus", "X Premium+"],
|
|
507
|
+
["premiumplus", "X Premium+"],
|
|
508
|
+
["xpremium", "X Premium+"],
|
|
509
|
+
["apikey", "API Key"],
|
|
510
|
+
["free", "Free"],
|
|
511
|
+
]);
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Map xAI's subscription tier to the name its own UI shows.
|
|
515
|
+
*
|
|
516
|
+
* Separators are folded away before matching so the same table covers whichever
|
|
517
|
+
* casing the API returns ("SuperGrok Heavy", "SUPER_GROK_HEAVY", "superGrokHeavy").
|
|
518
|
+
* Unknown values return null rather than being passed through: the Limits panel
|
|
519
|
+
* falls back to the bare brand name, which is better than rendering a raw enum
|
|
520
|
+
* (issue #130 shipped "Kimi Type_event" that way).
|
|
521
|
+
*/
|
|
522
|
+
function deriveGrokPlanLabel(rawTier) {
|
|
523
|
+
if (rawTier == null) return null;
|
|
524
|
+
const key = String(rawTier).toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
525
|
+
if (!key) return null;
|
|
526
|
+
// Exact match only. Prefix matching would read an unseen tier such as
|
|
527
|
+
// "SuperGrok Business" as plain "SuperGrok", and a confidently wrong plan on
|
|
528
|
+
// the card is worse than no plan at all.
|
|
529
|
+
return GROK_PLAN_TIERS.get(key) ?? null;
|
|
530
|
+
}
|
|
531
|
+
|
|
498
532
|
/**
|
|
499
533
|
* Parse either:
|
|
500
534
|
* - Unified billing (`?format=credits`): weekly/monthly period + creditUsagePercent
|
|
@@ -567,6 +601,8 @@ function normalizeGrokBillingResponse(body) {
|
|
|
567
601
|
}
|
|
568
602
|
|
|
569
603
|
return {
|
|
604
|
+
// Read from the response root, not from `config`.
|
|
605
|
+
plan_label: deriveGrokPlanLabel(body?.subscriptionTier),
|
|
570
606
|
period_type: periodType,
|
|
571
607
|
monthly_credits_limit: monthlyLimit,
|
|
572
608
|
monthly_credits_used: used,
|
|
@@ -721,6 +757,7 @@ module.exports = {
|
|
|
721
757
|
normalizeGrokPeriodType,
|
|
722
758
|
inferGrokPeriodTypeFromDates,
|
|
723
759
|
sumProductUsagePercent,
|
|
760
|
+
deriveGrokPlanLabel,
|
|
724
761
|
normalizeGrokBillingResponse,
|
|
725
762
|
fetchGrokBilling,
|
|
726
763
|
fetchGrokLimits,
|
|
@@ -26,7 +26,7 @@ function buildOpencodePlugin({ notifyPath }) {
|
|
|
26
26
|
return (
|
|
27
27
|
`// ${PLUGIN_MARKER}\n` +
|
|
28
28
|
`const notifyPath = ${JSON.stringify(safeNotifyPath)};\n` +
|
|
29
|
-
`
|
|
29
|
+
`const server = async ({ $ }) => {\n` +
|
|
30
30
|
` return {\n` +
|
|
31
31
|
` event: async ({ event }) => {\n` +
|
|
32
32
|
` if (!event || event.type !== ${JSON.stringify(DEFAULT_EVENT)}) return;\n` +
|
|
@@ -40,6 +40,25 @@ function buildOpencodePlugin({ notifyPath }) {
|
|
|
40
40
|
` } catch (_) {}\n` +
|
|
41
41
|
` }\n` +
|
|
42
42
|
` };\n` +
|
|
43
|
+
`};\n` +
|
|
44
|
+
// Three loaders read this one file (issue #646):
|
|
45
|
+
// * older opencode v1 takes the named export;
|
|
46
|
+
// * current v1 (verified on 1.18.30) requires a default export carrying
|
|
47
|
+
// `id` and `server()`, and rejects the file outright without them;
|
|
48
|
+
// * v2 requires a default export carrying `id` and `setup()`/`effect()`,
|
|
49
|
+
// and ignores `server()`.
|
|
50
|
+
// Emitting all three keeps a single generated file valid everywhere. v2's
|
|
51
|
+
// plugin context exposes no event stream and no shell (`@opencode-ai/plugin`
|
|
52
|
+
// v2 PluginContext is agent/aisdk/catalog/command/integration/reference/skill
|
|
53
|
+
// only), so there is nothing for `setup` to subscribe to -- it exists to make
|
|
54
|
+
// the plugin load instead of erroring on every startup. Usage itself does not
|
|
55
|
+
// depend on this hook: opencode rows are parsed from the on-disk storage and
|
|
56
|
+
// opencode.db, and the hook only makes a sync happen sooner.
|
|
57
|
+
`export const TokenTrackerPlugin = server;\n` +
|
|
58
|
+
`export default {\n` +
|
|
59
|
+
` id: "tokentracker",\n` +
|
|
60
|
+
` server,\n` +
|
|
61
|
+
` setup: async () => {},\n` +
|
|
43
62
|
`};\n`
|
|
44
63
|
);
|
|
45
64
|
}
|
|
@@ -159,6 +159,20 @@
|
|
|
159
159
|
"cache_read": 0.014,
|
|
160
160
|
"cache_write": 0.44
|
|
161
161
|
},
|
|
162
|
+
"deepseek-v4.1-flash": {
|
|
163
|
+
"input": 0.3,
|
|
164
|
+
"output": 1.2,
|
|
165
|
+
"cache_read": 0.006,
|
|
166
|
+
"cache_write": 0.3,
|
|
167
|
+
"note": "DeepSeek V4.1 Flash (official API id deepseek-flash, released 2026-09-10). deepseek-v4.1-flash is the id reported by OpenRouter, Command Code and WorkBuddy. Peak rates per https://api-docs.deepseek.com/quick_start/pricing/ ($0.30 / $1.20 / $0.006 cache read per MTok); computeRowCost halves them off-peak like the other DeepSeek V4 entries."
|
|
168
|
+
},
|
|
169
|
+
"deepseek-flash": {
|
|
170
|
+
"input": 0.3,
|
|
171
|
+
"output": 1.2,
|
|
172
|
+
"cache_read": 0.006,
|
|
173
|
+
"cache_write": 0.3,
|
|
174
|
+
"note": "Official DeepSeek API id for DeepSeek-V4.1-Flash. Same rates as deepseek-v4.1-flash."
|
|
175
|
+
},
|
|
162
176
|
"deepseek-chat": {
|
|
163
177
|
"input": 0.14,
|
|
164
178
|
"output": 0.28,
|
|
@@ -544,6 +558,14 @@
|
|
|
544
558
|
"match": "minimax-m2.7",
|
|
545
559
|
"ref": "MiniMax-M2.7"
|
|
546
560
|
},
|
|
561
|
+
{
|
|
562
|
+
"match": "deepseek-v4.1-flash",
|
|
563
|
+
"ref": "deepseek-v4.1-flash"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"match": "deepseek-flash",
|
|
567
|
+
"ref": "deepseek-flash"
|
|
568
|
+
},
|
|
547
569
|
{
|
|
548
570
|
"match": "deepseek-v4-flash",
|
|
549
571
|
"ref": "deepseek-v4-flash"
|
package/src/lib/pricing/index.js
CHANGED
|
@@ -33,6 +33,8 @@ const SOURCES_WITH_AUTHORITATIVE_COST = new Set(["grok"]);
|
|
|
33
33
|
const SEED_SNAPSHOT_PATH = path.resolve(__dirname, "seed-snapshot.json");
|
|
34
34
|
const DEEPSEEK_TIME_PRICED_MODELS = [
|
|
35
35
|
"deepseek-v4-flash",
|
|
36
|
+
"deepseek-v4.1-flash",
|
|
37
|
+
"deepseek-flash",
|
|
36
38
|
"deepseek-v4-pro",
|
|
37
39
|
];
|
|
38
40
|
// Sync seed load. Done at require-time so callers that haven't awaited
|
|
@@ -194,8 +196,14 @@ function computeRowCost(row) {
|
|
|
194
196
|
reportedCost > 0
|
|
195
197
|
) return reportedCost;
|
|
196
198
|
const pricing = getRowPricing(row);
|
|
199
|
+
// OmO, like Codex, reports reasoning as a subset of `output` (its own
|
|
200
|
+
// usage.cost bills no separate reasoning component), so charging it again
|
|
201
|
+
// here would double-bill every reasoning token.
|
|
197
202
|
const reasoningIncludedInOutput =
|
|
198
|
-
row.source === "codex" ||
|
|
203
|
+
row.source === "codex" ||
|
|
204
|
+
row.source === "acode" ||
|
|
205
|
+
row.source === "every-code" ||
|
|
206
|
+
row.source === "omo";
|
|
199
207
|
const reasoningCost = reasoningIncludedInOutput
|
|
200
208
|
? 0
|
|
201
209
|
: (row.reasoning_output_tokens || 0) * (pricing.output || 0);
|