tokentracker-cli 0.96.1 → 0.96.3
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 +34 -25
- package/README.ja.md +38 -27
- package/README.ko.md +39 -28
- package/README.md +36 -28
- package/README.zh-CN.md +36 -25
- package/dashboard/dist/assets/{AchievementBadge-DKyElm0T.js → AchievementBadge-BX4iBBYM.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BvCx3wND.js → AchievementsPage-DRYRuCbh.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DN69LzXX.js → AchievementsSection-BM_JWWhJ.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DGiKdZfz.js → BadgeDetailModal-DxoxXst2.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-zN8rH7_K.js → BotAnimated-BVpFxG8u.js} +1 -1
- package/dashboard/dist/assets/{Card-COWo-ClV.js → Card-BJJs701t.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-BWsB7Eat.js → ClawdAnimated-46wPZ_Tc.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-BfvIwuoJ.js → CommandPalette-B_zkc7IU.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BQWg7R_y.js → CommunityStatsModal-M2k5rIf1.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-C7ABSI-O.js → ConfirmModal-DBvOTcAQ.js} +1 -1
- package/dashboard/dist/assets/{Controls-vMzEZzej.js → Controls-BCkjPMSH.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-3bHhou09.js +1 -0
- package/dashboard/dist/assets/{DevicePage-3ejUBLWM.js → DevicePage-DFBUce8C.js} +1 -1
- package/dashboard/dist/assets/DialogClose-kvDd8h3V.js +1 -0
- package/dashboard/dist/assets/DialogDescription-BMyOicfz.js +1 -0
- package/dashboard/dist/assets/DialogTitle-DL5nvD31.js +1 -0
- package/dashboard/dist/assets/{FadeIn-D7Jcfz1N.js → FadeIn-B-Bg6SEy.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-D--Rx5BS.js → HeaderGithubStar-BExz0hh4.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-n1W0z3Wr.js → HoverTooltip-Dj5zemYT.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-D202tl9B.js → IpCheckPage-DN7FuJ_v.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-D6yL2EZo.js → LandingPage-9dKgSBRy.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-ChFg2mtA.js → LeaderboardAvatar-00V95XIg.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BXHwGs8r.js → LeaderboardPage-DkKfUt0Z.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-B5LoOpCk.js → LeaderboardProfileModal-C9sIHBgN.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-jsukhRz8.js → LeaderboardProfilePage-BAiLMsEb.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-BuenTaHa.js +8 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-Du0lz657.js → LocalOnlyNotice-CKhY27Lr.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BFEX9x7-.js → LoginCard-BbvoBx5N.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DqC7_d35.js → LoginModal-P7zIkBhO.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-Dzx8N7HE.js → LoginPage-Bo0bR26x.js} +1 -1
- package/dashboard/dist/assets/PetPage-C6i9bSSl.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-h0gf36vg.js +1 -0
- package/dashboard/dist/assets/ProviderIcon-Dm8fwPdj.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-x_GR79sR.js → ResetPasswordPage-DvSJ8kg0.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-BxDiDV4R.js → SegmentedControl-CP7oiGZE.js} +1 -1
- package/dashboard/dist/assets/Select-iwP92x9C.js +1 -0
- package/dashboard/dist/assets/SelectItemText-DzljA15_.js +1 -0
- package/dashboard/dist/assets/{ServiceStatusPage-BVQuVdjg.js → ServiceStatusPage-uD4D0Tid.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-DE39wNIO.js → SessionsPage-Cmb1ssNP.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-D8ryg1Pv.js +2 -0
- package/dashboard/dist/assets/SkillsPage-eXI09q03.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-BPM779Wb.js → TokenGalaxy-f2djrlUv.js} +1 -1
- package/dashboard/dist/assets/ToolbarRootContext-CDxCPYLv.js +1 -0
- package/dashboard/dist/assets/{TrendMonitor-BkNy-o1D.js → TrendMonitor-C419X22G.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-Bpg6H-ZB.js → WidgetsPage-AYq__pOO.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C6-NM-RO.js → WrappedPage-Bsm9k5XI.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-CvMMIjhi.css → __vite-browser-external-5P7me0Qq.css} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-BDkeh90N.js +45 -0
- package/dashboard/dist/assets/{agent-logos-DpbkRqXT.js → agent-logos-B6Vg-4_1.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BjOtWUVs.js → arrow-up-right-DIQXwaOD.js} +1 -1
- package/dashboard/dist/assets/{check-BlxnX5jZ.js → check-CgD_bV39.js} +1 -1
- package/dashboard/dist/assets/composite-BIgEqWZD.js +1 -0
- package/dashboard/dist/assets/{copy-CIsWjLY5.js → copy-DlM1BHdp.js} +1 -1
- package/dashboard/dist/assets/{download-QDaQbQdd.js → download-CQFG-VIY.js} +1 -1
- package/dashboard/dist/assets/{external-link-DPV4VU3m.js → external-link-BQyDqGxz.js} +1 -1
- package/dashboard/dist/assets/{flame-B_X-7VNp.js → flame-BamzyXzF.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DEx2TJj8.js → format-tokens-C4G-_xYt.js} +1 -1
- package/dashboard/dist/assets/{icons-983RyGSl.js → icons-Ntscvl1a.js} +1 -1
- package/dashboard/dist/assets/{index-qtonw_x4.js → index-CUrcPqgp.js} +1 -1
- package/dashboard/dist/assets/{index-BONYA8HZ.js → index-DGxgsNoQ.js} +1 -1
- package/dashboard/dist/assets/{index-Cjs5stC0.js → index-FRIt5mlv.js} +1 -1
- package/dashboard/dist/assets/{info-DpVOhXZI.js → info-CXytWcZi.js} +1 -1
- package/dashboard/dist/assets/{link-2-CBro-upz.js → link-2-Dexoo_jO.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-wYFdSyOd.js → loader-circle-Cn0JHc8t.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-DhTyQ4ly.js → maximize-2-DLh7gYuP.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CPljwVhN.js → modifiers.esm-DOh_xtyt.js} +1 -1
- package/dashboard/dist/assets/module-DUSXqJtA.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-D_wr-HOR.js → monitor-smartphone-CpqS9Ior.js} +1 -1
- package/dashboard/dist/assets/{plus-BNjfkBEF.js → plus-bfQV7nyv.js} +1 -1
- package/dashboard/dist/assets/provider-display-Bf9pW06U.js +42 -0
- package/dashboard/dist/assets/{react-yHv9MTUA.js → react-BmFZKA_b.js} +1 -1
- package/dashboard/dist/assets/{search-Dfak92ZA.js → search-l3jJ2fzC.js} +1 -1
- package/dashboard/dist/assets/{skills-api-By2PdNa1.js → skills-api-CWmLnaxn.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-B9xy66_v.js → sortable.esm-DLgyXLHM.js} +1 -1
- package/dashboard/dist/assets/store-JM-a8ElZ.js +1 -0
- package/dashboard/dist/assets/{terminal-pbXWBmZM.js → terminal-6aY2Sn8P.js} +1 -1
- package/dashboard/dist/assets/{trash-2-CboDPwbW.js → trash-D5oon5pT.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-D6HTdnHq.js → use-community-stats-ZWUMFwzc.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-hwSr9Cam.js → use-latest-request-guard-BDaXr4gK.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-yXXToNIc.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-OtfOqrNb.js → use-ordered-list-C6RG8N6c.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BqDKim_3.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-BScvtS8F.js → use-session-efficiency-pref-DpgZO_Lx.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Cy1ykjEs.js → use-transform-D42P7jkd.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-CV6puFwq.js +1 -0
- package/dashboard/dist/assets/{useCurrency-VqF3cy1C.js → useCurrency-GWBmOrCR.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-Cgub4MXu.js → useTokenFormat-Beybkb0v.js} +1 -1
- package/dashboard/dist/assets/{zap-DStzvQJI.js → zap-CEq2616W.js} +1 -1
- package/dashboard/dist/brand-logos/acode-dark.png +0 -0
- package/dashboard/dist/brand-logos/acode.png +0 -0
- package/dashboard/dist/brand-logos/commandcode.svg +1 -0
- package/dashboard/dist/brand-logos/devin.svg +6 -0
- package/dashboard/dist/index.html +20 -16
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/init.js +114 -4
- package/src/commands/status.js +37 -0
- package/src/commands/sync.js +92 -10
- package/src/commands/uninstall.js +23 -0
- package/src/lib/codex-config.js +42 -0
- package/src/lib/codex-rollout-parser.js +2 -2
- package/src/lib/commandcode-limits.js +317 -0
- package/src/lib/devin-limits.js +249 -0
- package/src/lib/devin-usage.js +212 -0
- package/src/lib/local-api.js +14 -0
- package/src/lib/passive-mode.js +13 -1
- package/src/lib/pricing/curated-overrides.json +78 -1
- package/src/lib/pricing/index.js +11 -9
- package/src/lib/pricing/matcher.js +28 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1473 -147
- package/src/lib/session-analytics.js +2 -2
- package/src/lib/skills-manager.js +5 -0
- package/src/lib/subscriptions.js +70 -16
- package/src/lib/usage-limits.js +240 -49
- package/dashboard/dist/assets/DashboardPage-QBhbCMZF.js +0 -1
- package/dashboard/dist/assets/DialogClose-D_dvtfGV.js +0 -1
- package/dashboard/dist/assets/DialogDescription-ryGYdQjX.js +0 -1
- package/dashboard/dist/assets/DialogTitle-_ahf0z8b.js +0 -1
- package/dashboard/dist/assets/LimitsPage-PvqkOOwp.js +0 -6
- package/dashboard/dist/assets/PetPage-CNlWHF4z.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-D9jadD3j.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-Caz5PuAm.js +0 -1
- package/dashboard/dist/assets/Select-B5o-1sEl.js +0 -1
- package/dashboard/dist/assets/SelectItemText-CiA6L7Wk.js +0 -1
- package/dashboard/dist/assets/SettingsPage-DJrb_fsK.js +0 -2
- package/dashboard/dist/assets/SkillsPage-COk-YQQ5.js +0 -1
- package/dashboard/dist/assets/ToolbarRootContext-CozJT6Vs.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-BGnq8Ebm.js +0 -45
- package/dashboard/dist/assets/composite-Dxmivlk4.js +0 -1
- package/dashboard/dist/assets/limits-providers-BUoquSA-.js +0 -1
- package/dashboard/dist/assets/module-xpSFyNBK.js +0 -3
- package/dashboard/dist/assets/provider-display-BOBtUyFi.js +0 -42
- package/dashboard/dist/assets/store-oQZR4d9M.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-B-pbwUS8.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DGXcZBX3.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-CSMb2zYW.js +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Devin CLI (Cognition — devin.ai) local history projection.
|
|
4
|
+
//
|
|
5
|
+
// Devin persists chat history in a SQLite database at
|
|
6
|
+
// `$XDG_DATA_HOME/devin/cli/sessions.db` (default
|
|
7
|
+
// `~/.local/share/devin/cli/sessions.db`). Token usage lives on assistant
|
|
8
|
+
// message nodes inside the `chat_message` JSON document. The SQL below is a
|
|
9
|
+
// deliberately narrow projection: request identity, generation model, the
|
|
10
|
+
// original generation timestamps, the numeric usage metrics, and the owning
|
|
11
|
+
// session's `working_directory`/`created_at` for canonical ordering and local
|
|
12
|
+
// project attribution. Message bodies, prompt text, node `metadata`,
|
|
13
|
+
// `sessions.cogs_json`, and any credential-bearing state are never selected.
|
|
14
|
+
//
|
|
15
|
+
// Verified against devin CLI 3000.10.21 (611c1cba): replay/fork/compaction
|
|
16
|
+
// produces multiple `message_nodes` rows that share one
|
|
17
|
+
// `metadata.request_id` with identical metrics and original timestamps, so
|
|
18
|
+
// `request_id` — never `row_id` or `(session_id, request_id)` — is the
|
|
19
|
+
// canonical usage identity across the whole local install.
|
|
20
|
+
//
|
|
21
|
+
// Token components are disjoint: the DB's `metrics.input_tokens` already
|
|
22
|
+
// excludes `cache_read_tokens` and `cache_creation_tokens`, so each reported
|
|
23
|
+
// counter is added once and caches are never subtracted from input.
|
|
24
|
+
|
|
25
|
+
const DEVIN_TABLE_PROBE_SQL =
|
|
26
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('message_nodes','sessions')";
|
|
27
|
+
|
|
28
|
+
const metricField = (jsonPath, alias) =>
|
|
29
|
+
`CASE WHEN json_valid(n.chat_message) THEN json_extract(n.chat_message, '${jsonPath}') ELSE NULL END AS ${alias}`;
|
|
30
|
+
|
|
31
|
+
// `sessions` is LEFT JOINed only for `working_directory` and `created_at`;
|
|
32
|
+
// older databases may not have the table, in which case the projection runs
|
|
33
|
+
// without it (project attribution and session-age ordering degrade to nulls).
|
|
34
|
+
function devinUsageSql({ hasSessionsTable = true } = {}) {
|
|
35
|
+
const join = hasSessionsTable ? "LEFT JOIN sessions s ON s.id = n.session_id" : "";
|
|
36
|
+
const sessionCreatedAt = hasSessionsTable ? "s.created_at" : "NULL";
|
|
37
|
+
const workingDirectory = hasSessionsTable ? "s.working_directory" : "NULL";
|
|
38
|
+
return `
|
|
39
|
+
SELECT
|
|
40
|
+
n.row_id AS row_id,
|
|
41
|
+
n.session_id AS session_id,
|
|
42
|
+
${sessionCreatedAt} AS session_created_at,
|
|
43
|
+
${workingDirectory} AS working_directory,
|
|
44
|
+
${metricField("$.metadata.request_id", "request_id")},
|
|
45
|
+
${metricField("$.metadata.generation_model", "generation_model")},
|
|
46
|
+
${metricField("$.metadata.started_generation_at", "started_generation_at")},
|
|
47
|
+
${metricField("$.metadata.created_at", "message_created_at")},
|
|
48
|
+
${metricField("$.metadata.metrics.input_tokens", "input_tokens")},
|
|
49
|
+
${metricField("$.metadata.metrics.output_tokens", "output_tokens")},
|
|
50
|
+
${metricField("$.metadata.metrics.cache_read_tokens", "cache_read_tokens")},
|
|
51
|
+
${metricField("$.metadata.metrics.cache_creation_tokens", "cache_creation_tokens")}
|
|
52
|
+
FROM message_nodes n
|
|
53
|
+
${join}
|
|
54
|
+
WHERE json_valid(n.chat_message)
|
|
55
|
+
AND json_extract(n.chat_message, '$.role') = 'assistant'
|
|
56
|
+
AND json_extract(n.chat_message, '$.metadata.request_id') IS NOT NULL
|
|
57
|
+
ORDER BY n.row_id
|
|
58
|
+
`.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeText(value) {
|
|
62
|
+
if (typeof value !== "string") return null;
|
|
63
|
+
const trimmed = value.trim();
|
|
64
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Strict integer check: usage fields must be finite non-negative safe
|
|
68
|
+
// integers. `null`/`undefined` cache counters are legal (no reported cache
|
|
69
|
+
// contribution) — callers pass them through `optionalCount`; required
|
|
70
|
+
// counters go through `requiredCount`.
|
|
71
|
+
function toSafeCount(value) {
|
|
72
|
+
const n = Number(value);
|
|
73
|
+
return Number.isSafeInteger(n) && n >= 0 ? n : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requiredCount(value) {
|
|
77
|
+
// A missing/NULL required counter is incomplete usage, not zero — pending
|
|
78
|
+
// assistant records carry no metrics block and must stay uncounted until
|
|
79
|
+
// the writer finalizes them.
|
|
80
|
+
if (value === null || value === undefined) return null;
|
|
81
|
+
return toSafeCount(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function optionalCount(value) {
|
|
85
|
+
if (value === null || value === undefined) return 0;
|
|
86
|
+
return toSafeCount(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// RFC3339 timestamps arrive with both `Z` and `+00:00` spellings; Date.parse
|
|
90
|
+
// normalizes both to the same instant. Returns epoch ms or 0 when missing or
|
|
91
|
+
// unparsable.
|
|
92
|
+
function parseDevinTimestampMs(value) {
|
|
93
|
+
if (typeof value !== "string" || !value.trim()) return 0;
|
|
94
|
+
const ms = Date.parse(value.trim());
|
|
95
|
+
return Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// session.created_at is Unix seconds; node row ordering uses ms.
|
|
99
|
+
function epochSecondsToMs(value) {
|
|
100
|
+
const n = Number(value);
|
|
101
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) * 1000 : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Normalize one projected row into a canonical candidate, or null when the
|
|
105
|
+
// record is pending/malformed: missing request identity, missing/invalid
|
|
106
|
+
// numeric usage, or no usable original timestamp. Such rows are re-evaluated
|
|
107
|
+
// on every scan, so a record that gains metrics or a timestamp later is
|
|
108
|
+
// counted then.
|
|
109
|
+
function normalizeDevinUsageRow(row) {
|
|
110
|
+
const requestId = normalizeText(row?.request_id);
|
|
111
|
+
if (!requestId) return null;
|
|
112
|
+
|
|
113
|
+
const input = requiredCount(row?.input_tokens);
|
|
114
|
+
const output = requiredCount(row?.output_tokens);
|
|
115
|
+
const cacheRead = optionalCount(row?.cache_read_tokens);
|
|
116
|
+
const cacheCreation = optionalCount(row?.cache_creation_tokens);
|
|
117
|
+
if (input === null || output === null || cacheRead === null || cacheCreation === null) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The original generation time decides the bucket; the node insertion time
|
|
122
|
+
// (n.created_at) is clone/replay insertion time and is never a fallback.
|
|
123
|
+
const tsMs =
|
|
124
|
+
parseDevinTimestampMs(row?.started_generation_at) ||
|
|
125
|
+
parseDevinTimestampMs(row?.message_created_at);
|
|
126
|
+
if (!tsMs) return null;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
requestId,
|
|
130
|
+
sessionId: normalizeText(row?.session_id) || "",
|
|
131
|
+
rowId: toSafeCount(row?.row_id) ?? 0,
|
|
132
|
+
sessionCreatedAtMs: epochSecondsToMs(row?.session_created_at),
|
|
133
|
+
workingDirectory: normalizeText(row?.working_directory),
|
|
134
|
+
model: normalizeText(row?.generation_model) || "unknown",
|
|
135
|
+
tsMs,
|
|
136
|
+
input,
|
|
137
|
+
output,
|
|
138
|
+
cacheRead,
|
|
139
|
+
cacheCreation,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Pick the canonical retained record for one request_id. Copies created by
|
|
144
|
+
// replay/fork/compaction carry identical original timestamps and metrics, so
|
|
145
|
+
// the order is: earliest original generation time, then the earliest-created
|
|
146
|
+
// owning session (a fork's copy lives in a younger session), then session id,
|
|
147
|
+
// then newest row first so an in-place correction appended as a fresh node
|
|
148
|
+
// inside the same session wins over the stale sibling.
|
|
149
|
+
function compareCandidates(a, b) {
|
|
150
|
+
if (a.tsMs !== b.tsMs) return a.tsMs - b.tsMs;
|
|
151
|
+
const sessionA = a.sessionCreatedAtMs ?? Number.POSITIVE_INFINITY;
|
|
152
|
+
const sessionB = b.sessionCreatedAtMs ?? Number.POSITIVE_INFINITY;
|
|
153
|
+
if (sessionA !== sessionB) return sessionA - sessionB;
|
|
154
|
+
if (a.sessionId !== b.sessionId) return a.sessionId < b.sessionId ? -1 : 1;
|
|
155
|
+
return b.rowId - a.rowId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build the deduplicated per-request event list. Conversation ownership and
|
|
159
|
+
// session/project attribution are decided by the persisted request ledger in
|
|
160
|
+
// parseDevinIncremental — the projection only reports each request's
|
|
161
|
+
// best-evidence retained record; `conversation_count` stays 0 here and is
|
|
162
|
+
// filled from ledger state at reconcile time.
|
|
163
|
+
function buildDevinUsageEvents(rows) {
|
|
164
|
+
const byRequest = new Map();
|
|
165
|
+
let rowsSeen = 0;
|
|
166
|
+
let rowsSkipped = 0;
|
|
167
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
168
|
+
rowsSeen += 1;
|
|
169
|
+
const candidate = normalizeDevinUsageRow(row);
|
|
170
|
+
if (!candidate) {
|
|
171
|
+
rowsSkipped += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const list = byRequest.get(candidate.requestId);
|
|
175
|
+
if (list) list.push(candidate);
|
|
176
|
+
else byRequest.set(candidate.requestId, [candidate]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const events = [];
|
|
180
|
+
for (const [requestId, candidates] of byRequest) {
|
|
181
|
+
candidates.sort(compareCandidates);
|
|
182
|
+
const canonical = candidates[0];
|
|
183
|
+
const total = canonical.input + canonical.output + canonical.cacheRead + canonical.cacheCreation;
|
|
184
|
+
events.push({
|
|
185
|
+
requestId,
|
|
186
|
+
sessionId: canonical.sessionId,
|
|
187
|
+
workingDirectory: canonical.workingDirectory,
|
|
188
|
+
model: canonical.model,
|
|
189
|
+
tsMs: canonical.tsMs,
|
|
190
|
+
totals: {
|
|
191
|
+
input_tokens: canonical.input,
|
|
192
|
+
cached_input_tokens: canonical.cacheRead,
|
|
193
|
+
cache_creation_input_tokens: canonical.cacheCreation,
|
|
194
|
+
output_tokens: canonical.output,
|
|
195
|
+
reasoning_output_tokens: 0,
|
|
196
|
+
total_tokens: total,
|
|
197
|
+
billable_total_tokens: total,
|
|
198
|
+
total_cost_usd: 0,
|
|
199
|
+
conversation_count: 0,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
events.sort((a, b) => a.tsMs - b.tsMs || (a.requestId < b.requestId ? -1 : 1));
|
|
204
|
+
|
|
205
|
+
return { events, rowsSeen, rowsSkipped };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
DEVIN_TABLE_PROBE_SQL,
|
|
210
|
+
devinUsageSql,
|
|
211
|
+
buildDevinUsageEvents,
|
|
212
|
+
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -3215,6 +3215,19 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
3215
3215
|
if (p === "/functions/tokentracker-usage-limits") {
|
|
3216
3216
|
const { getUsageLimits, resetUsageLimitsCache } = require("./usage-limits");
|
|
3217
3217
|
try {
|
|
3218
|
+
// Devin quota is opt-in (Settings > Usage & Limits > Providers). An
|
|
3219
|
+
// explicit devin=1 without local authentication is rejected before any
|
|
3220
|
+
// cache reset or provider work — silently downgrading it to a disabled
|
|
3221
|
+
// response would misreport an enabled client as a disabled provider.
|
|
3222
|
+
// Authorization is evaluated unconditionally so the check never depends
|
|
3223
|
+
// on the user-controlled opt-in flag itself.
|
|
3224
|
+
const localAuthorized = isAuthorizedLocalMutation(req);
|
|
3225
|
+
const devinParam = url.searchParams.get("devin");
|
|
3226
|
+
const devinEnabled = devinParam === "1" || devinParam === "true";
|
|
3227
|
+
if (devinEnabled && !localAuthorized) {
|
|
3228
|
+
json(res, { error: "Unauthorized" }, 401);
|
|
3229
|
+
return true;
|
|
3230
|
+
}
|
|
3218
3231
|
const refreshParam = url.searchParams.get("refresh");
|
|
3219
3232
|
const forceRefresh = refreshParam === "1" || refreshParam === "true";
|
|
3220
3233
|
if (forceRefresh) {
|
|
@@ -3227,6 +3240,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
3227
3240
|
// Punches through the Claude disk fresh-cache (but not the 429
|
|
3228
3241
|
// cooldown) — an explicit user refresh should hit upstream.
|
|
3229
3242
|
forceRefresh,
|
|
3243
|
+
devinEnabled,
|
|
3230
3244
|
});
|
|
3231
3245
|
json(res, data);
|
|
3232
3246
|
} catch (e) {
|
package/src/lib/passive-mode.js
CHANGED
|
@@ -89,7 +89,7 @@ function classifyWritableFailure(filePath) {
|
|
|
89
89
|
* every_code, opencode, openclaw, codebuddy, workbuddy, grok }
|
|
90
90
|
* @returns {PassiveProvider[]}
|
|
91
91
|
*/
|
|
92
|
-
function detectPassiveProviders({ home, hookStatus }) {
|
|
92
|
+
function detectPassiveProviders({ home, hookStatus, env = process.env }) {
|
|
93
93
|
const out = [];
|
|
94
94
|
|
|
95
95
|
// Claude Code — logs at ~/.claude/projects/<name>/*.jsonl
|
|
@@ -128,6 +128,18 @@ function detectPassiveProviders({ home, hookStatus }) {
|
|
|
128
128
|
settingsPath: path.join(home, ".codex", "config.toml"),
|
|
129
129
|
}));
|
|
130
130
|
|
|
131
|
+
const acodeHome = env.TOKENTRACKER_ACODE_HOME || path.join(home, ".acode");
|
|
132
|
+
out.push(buildEntry({
|
|
133
|
+
name: "acode",
|
|
134
|
+
hookExpected: true,
|
|
135
|
+
hookInstalled: Boolean(hookStatus?.acode_notify ?? hookStatus?.acode),
|
|
136
|
+
logsDir: acodeHome,
|
|
137
|
+
logsPredicate: (_full, name, isDir) =>
|
|
138
|
+
(isDir && (name === "sessions" || name === "archived_sessions")) ||
|
|
139
|
+
(!isDir && name.endsWith(".jsonl")),
|
|
140
|
+
settingsPath: path.join(acodeHome, "config.toml"),
|
|
141
|
+
}));
|
|
142
|
+
|
|
131
143
|
// Every Code — same shape as Codex, different home
|
|
132
144
|
out.push(buildEntry({
|
|
133
145
|
name: "every_code",
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"_meta": {
|
|
3
3
|
"note": "Curated price overrides. Always wins over LiteLLM. Two reasons to live here: (1) self-defined alias names that LiteLLM will never carry (kiro-*, hy3-*, composer-*, kimi-for-coding, free-tier OpenRouter routes); (2) prices we want to pin even if LiteLLM has the model (e.g. cache_write fields LiteLLM often omits). Units: USD per million tokens. Edit this file to override pricing without redeploying.",
|
|
4
4
|
"units": "usd_per_million_tokens",
|
|
5
|
-
"deepseek_v4_pricing_note": "Official DeepSeek V4 pricing verified on 2026-08-21. Peak hours are 01:00-04:00 and 06:00-10:00 UTC; all other hours are off-peak at half price. Exact entries store peak rates. computeRowCost applies the time-of-use discount from each row's UTC hour_start."
|
|
5
|
+
"deepseek_v4_pricing_note": "Official DeepSeek V4 pricing verified on 2026-08-21. Peak hours are 01:00-04:00 and 06:00-10:00 UTC; all other hours are off-peak at half price. Exact entries store peak rates. computeRowCost applies the time-of-use discount from each row's UTC hour_start.",
|
|
6
|
+
"iflytek_maas_pricing_note": "iFlytek MaaS inference prices supplied for AStudio on 2026-08-31. AStudio homepage: https://agent.xfyun.cn/. Official pricing source: https://maas.xfyun.cn/modelSquare. Source values are RMB per million tokens, converted at 7.2 RMB/USD, then rounded to two decimal places for billing. Models without a dedicated cache-hit price use the normal input rate; cache writes also use the normal input rate. iFlytek MaaS prices are source-scoped and do not inherit DeepSeek time-of-use discounts."
|
|
6
7
|
},
|
|
7
8
|
"exact": {
|
|
8
9
|
"claude-fable-5": {
|
|
@@ -47,6 +48,13 @@
|
|
|
47
48
|
"cache_write": 1.25,
|
|
48
49
|
"note": "Canonical undated alias emitted by Cursor. Pin to the same rates as claude-haiku-4-5-20251001 so local cost does not depend on which dated LiteLLM aliases are present in the current cache."
|
|
49
50
|
},
|
|
51
|
+
"gpt-6-astra": {
|
|
52
|
+
"input": 10,
|
|
53
|
+
"output": 50,
|
|
54
|
+
"cache_read": 1,
|
|
55
|
+
"cache_write": 12.5,
|
|
56
|
+
"note": "Official Standard USD/MTok rates verified 2026-09-07: https://developers.openai.com/api/docs/models/gpt-6-astra. Requests above 272K input tokens use 2x input/cache and 1.5x output rates; computeRowCost applies this only to observed long-context request subsets. Cloud aggregates use Standard short-context estimates because they do not retain per-request context or service tier. Reasoning-effort suffixes use the same base rates."
|
|
57
|
+
},
|
|
50
58
|
"gpt-5.6-sol": {
|
|
51
59
|
"input": 4,
|
|
52
60
|
"output": 20,
|
|
@@ -413,7 +421,76 @@
|
|
|
413
421
|
"alias": {
|
|
414
422
|
"auto": "composer-1"
|
|
415
423
|
},
|
|
424
|
+
"source_exact": {
|
|
425
|
+
"acode": {
|
|
426
|
+
"xopglm53": { "input": 1.11, "output": 3.89, "cache_read": 0.28, "cache_write": 1.11 },
|
|
427
|
+
"xopdeepseekv4pro0813": { "input": 1.25, "output": 3.75, "cache_read": 0.04, "cache_write": 1.25 },
|
|
428
|
+
"xopdeepseekv4flash0731": { "input": 0.14, "output": 0.28, "cache_read": 0.03, "cache_write": 0.14 },
|
|
429
|
+
"xopkimik27code": { "input": 0.90, "output": 3.75, "cache_read": 0.90, "cache_write": 0.90 },
|
|
430
|
+
"xopglm52": { "input": 1.11, "output": 3.89, "cache_read": 0.28, "cache_write": 1.11 },
|
|
431
|
+
"xopdeepseekv4flash": { "input": 0.14, "output": 0.28, "cache_read": 0.03, "cache_write": 0.14 },
|
|
432
|
+
"xopkimik26": { "input": 0.90, "output": 3.75, "cache_read": 0.18, "cache_write": 0.90 },
|
|
433
|
+
"xopdeepseekv4pro": { "input": 1.67, "output": 3.33, "cache_read": 0.14, "cache_write": 1.67 },
|
|
434
|
+
"xopqwen36v35b": { "input": 0.15, "output": 0.90, "cache_read": 0.15, "cache_write": 0.15 },
|
|
435
|
+
"xophunyuan7bmt": { "input": 0.07, "output": 0.28, "cache_read": 0.07, "cache_write": 0.07 },
|
|
436
|
+
"xoppaddleocrv16": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
437
|
+
"xsparkx2flash": { "input": 0.14, "output": 0.28, "cache_read": 0.14, "cache_write": 0.14 },
|
|
438
|
+
"xopglm51": { "input": 1.11, "output": 3.89, "cache_read": 0.22, "cache_write": 1.11 },
|
|
439
|
+
"xsparkx2": { "input": 0.42, "output": 0.42, "cache_read": 0.42, "cache_write": 0.42 },
|
|
440
|
+
"xop35qwen2b": { "input": 0.03, "output": 0.06, "cache_read": 0.03, "cache_write": 0.03 },
|
|
441
|
+
"xopqwen35397b": { "input": 0.17, "output": 1.00, "cache_read": 0.17, "cache_write": 0.17 },
|
|
442
|
+
"xminimaxm25": { "input": 0.29, "output": 1.17, "cache_read": 0.29, "cache_write": 0.29 },
|
|
443
|
+
"xopglm5": { "input": 0.83, "output": 3.06, "cache_read": 0.17, "cache_write": 0.83 },
|
|
444
|
+
"xopkimik25": { "input": 0.56, "output": 2.92, "cache_read": 0.56, "cache_write": 0.56 },
|
|
445
|
+
"xopdeepseekv32": { "input": 0.14, "output": 0.21, "cache_read": 0.14, "cache_write": 0.14 },
|
|
446
|
+
"xop3qwencodernext": { "input": 0.35, "output": 1.39, "cache_read": 0.35, "cache_write": 0.35 },
|
|
447
|
+
"xopglmv47flash": { "input": 0.14, "output": 0.21, "cache_read": 0.14, "cache_write": 0.14 },
|
|
448
|
+
"xopglm47blth2": { "input": 0.56, "output": 2.22, "cache_read": 0.56, "cache_write": 0.56 },
|
|
449
|
+
"xop3qwen32bvl": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
450
|
+
"xopdeepseekocr": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
451
|
+
"xophunyuanocr": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
452
|
+
"xop3qwen80bnext": { "input": 0.08, "output": 0.33, "cache_read": 0.08, "cache_write": 0.08 },
|
|
453
|
+
"xop3qwen235b2507": { "input": 0.17, "output": 1.67, "cache_read": 0.17, "cache_write": 0.17 },
|
|
454
|
+
"xop3qwen30b2507": { "input": 0.06, "output": 0.63, "cache_read": 0.06, "cache_write": 0.06 },
|
|
455
|
+
"xop3qwen235b": { "input": 0.17, "output": 1.67, "cache_read": 0.17, "cache_write": 0.17 },
|
|
456
|
+
"xop3qwen30b": { "input": 0.06, "output": 0.63, "cache_read": 0.06, "cache_write": 0.06 },
|
|
457
|
+
"xop3qwen32b": { "input": 0.17, "output": 1.67, "cache_read": 0.17, "cache_write": 0.17 },
|
|
458
|
+
"xdeepseekv3": { "input": 0.22, "output": 0.89, "cache_read": 0.22, "cache_write": 0.22 },
|
|
459
|
+
"xdeepseekr1": { "input": 0.44, "output": 1.78, "cache_read": 0.44, "cache_write": 0.44 },
|
|
460
|
+
"xdeepseekr1qwen32b": { "input": 0.22, "output": 0.67, "cache_read": 0.22, "cache_write": 0.22 },
|
|
461
|
+
"xopkimik2blth": { "input": 0.56, "output": 2.22, "cache_read": 0.56, "cache_write": 0.56 },
|
|
462
|
+
"xopkimik2blins": { "input": 0.56, "output": 2.22, "cache_read": 0.56, "cache_write": 0.56 },
|
|
463
|
+
"xop3qwen8breranker": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
464
|
+
"xop3qwen8bembedding": { "input": 0.00, "output": 0.00, "cache_read": 0.00, "cache_write": 0.00 },
|
|
465
|
+
"xop3qwen0b6": { "input": 0.04, "output": 0.42, "cache_read": 0.04, "cache_write": 0.04 },
|
|
466
|
+
"xop3qwen4b": { "input": 0.04, "output": 0.42, "cache_read": 0.04, "cache_write": 0.04 },
|
|
467
|
+
"xqwen257bchat": { "input": 0.07, "output": 0.14, "cache_read": 0.07, "cache_write": 0.07 },
|
|
468
|
+
"xop3qwen14b": { "input": 0.14, "output": 1.39, "cache_read": 0.14, "cache_write": 0.14 },
|
|
469
|
+
"xop3qwen8b": { "input": 0.07, "output": 0.69, "cache_read": 0.07, "cache_write": 0.07 },
|
|
470
|
+
"xsparkprox": { "input": 1.11, "output": 5.56, "cache_read": 1.11, "cache_write": 1.11 },
|
|
471
|
+
"xspark13b6k": { "input": 0.28, "output": 0.83, "cache_read": 0.28, "cache_write": 0.28 },
|
|
472
|
+
"spark mini": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
473
|
+
"spark mini instruct": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
474
|
+
"spark tiny": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
475
|
+
"internlm2.5_7b_chat": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
476
|
+
"internlm2.5_1.8b_chat": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
477
|
+
"qwen_v2.5_7b_base": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
478
|
+
"xsqwen2d53b": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
479
|
+
"qwen_v2.5_3b_base": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
480
|
+
"qwen_v2.5_1.5b_instruct": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
481
|
+
"qwen_v2.5_1.5b_base": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
482
|
+
"qwen_v2.5_0.5b_instruct": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
483
|
+
"qwen_v2.5_0.5b_base": { "input": 0.28, "output": 1.11, "cache_read": 0.28, "cache_write": 0.28 },
|
|
484
|
+
"xsqwenv2s1b5c": { "input": 0.14, "output": 0.28, "cache_read": 0.14, "cache_write": 0.14 },
|
|
485
|
+
"xsqwenv2s0b5c": { "input": 0.28, "output": 0.56, "cache_read": 0.28, "cache_write": 0.28 },
|
|
486
|
+
"xqwen14bchat": { "input": 0.28, "output": 0.83, "cache_read": 0.28, "cache_write": 0.28 }
|
|
487
|
+
}
|
|
488
|
+
},
|
|
416
489
|
"fuzzy": [
|
|
490
|
+
{
|
|
491
|
+
"match": "gpt-6-astra",
|
|
492
|
+
"ref": "gpt-6-astra"
|
|
493
|
+
},
|
|
417
494
|
{
|
|
418
495
|
"match": "gpt-5.6-sol",
|
|
419
496
|
"ref": "gpt-5.6-sol"
|
package/src/lib/pricing/index.js
CHANGED
|
@@ -25,10 +25,9 @@ const PI_SUBSCRIPTION_SOURCES = new Set([
|
|
|
25
25
|
// server (including LM Link). Secure Cloud usage has a separate billing path
|
|
26
26
|
// and is not present in these logs.
|
|
27
27
|
const LOCAL_INFERENCE_SOURCES = new Set(["lmstudio"]);
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// repricing stays in place for when OpenAI ships a larger Codex window.
|
|
28
|
+
// OpenAI long-context pricing depends on a single request's raw input,
|
|
29
|
+
// including cached tokens, never a session/day aggregate. Astra supports
|
|
30
|
+
// a larger context window; only observed request subsets receive the premium.
|
|
32
31
|
const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272_000;
|
|
33
32
|
const SOURCES_WITH_AUTHORITATIVE_COST = new Set(["grok"]);
|
|
34
33
|
const SEED_SNAPSHOT_PATH = path.resolve(__dirname, "seed-snapshot.json");
|
|
@@ -36,7 +35,6 @@ const DEEPSEEK_TIME_PRICED_MODELS = [
|
|
|
36
35
|
"deepseek-v4-flash",
|
|
37
36
|
"deepseek-v4-pro",
|
|
38
37
|
];
|
|
39
|
-
|
|
40
38
|
// Sync seed load. Done at require-time so callers that haven't awaited
|
|
41
39
|
// ensurePricingLoaded() (e.g. tests, vite mock startup, edge functions) still
|
|
42
40
|
// get LiteLLM-backed pricing instead of all-zero. ensurePricingLoaded() will
|
|
@@ -164,6 +162,9 @@ function isDeepSeekOffPeak(row) {
|
|
|
164
162
|
|
|
165
163
|
function getRowPricing(row) {
|
|
166
164
|
const pricing = getModelPricing(row?.model, { source: row?.source });
|
|
165
|
+
// AStudio uses iFlytek MaaS fixed prices and does not inherit DeepSeek public API
|
|
166
|
+
// time-based discounts.
|
|
167
|
+
if (String(row?.source || "").toLowerCase() === "acode") return pricing;
|
|
167
168
|
if (!isDeepSeekTimePricedModel(row?.model) || !isDeepSeekOffPeak(row)) return pricing;
|
|
168
169
|
return {
|
|
169
170
|
...pricing,
|
|
@@ -193,7 +194,8 @@ function computeRowCost(row) {
|
|
|
193
194
|
reportedCost > 0
|
|
194
195
|
) return reportedCost;
|
|
195
196
|
const pricing = getRowPricing(row);
|
|
196
|
-
const reasoningIncludedInOutput =
|
|
197
|
+
const reasoningIncludedInOutput =
|
|
198
|
+
row.source === "codex" || row.source === "acode" || row.source === "every-code";
|
|
197
199
|
const reasoningCost = reasoningIncludedInOutput
|
|
198
200
|
? 0
|
|
199
201
|
: (row.reasoning_output_tokens || 0) * (pricing.output || 0);
|
|
@@ -207,9 +209,9 @@ function computeRowCost(row) {
|
|
|
207
209
|
);
|
|
208
210
|
|
|
209
211
|
const model = String(row?.model || "").toLowerCase();
|
|
210
|
-
const
|
|
211
|
-
model === "gpt-5.6" || model.includes("gpt-5.6-sol");
|
|
212
|
-
if (!
|
|
212
|
+
const usesOpenAILongContextTier =
|
|
213
|
+
model === "gpt-5.6" || model.includes("gpt-5.6-sol") || model.includes("gpt-6-astra");
|
|
214
|
+
if (!usesOpenAILongContextTier) return baseCost;
|
|
213
215
|
|
|
214
216
|
const bounded = (value, total) => Math.min(
|
|
215
217
|
Math.max(0, Number(value) || 0),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Pure pricing-lookup logic. No I/O, no async. Tested in isolation.
|
|
2
2
|
//
|
|
3
3
|
// Resolve order:
|
|
4
|
+
// 0. CURATED source exact match (source-specific pricing)
|
|
4
5
|
// 1. CURATED exact match (self-defined aliases like kiro-*, hy3-*)
|
|
5
6
|
// 2. LiteLLM exact match (mainstream claude/gpt-5/gemini)
|
|
6
7
|
// 3. CURATED alias (e.g. "auto" -> "composer-1")
|
|
@@ -164,6 +165,15 @@ function normalizeWorkbuddyModel(model) {
|
|
|
164
165
|
return model;
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
function normalizeIFlytekMaasModel(model) {
|
|
169
|
+
if (!model || typeof model !== "string") return model;
|
|
170
|
+
const trimmed = model.trim();
|
|
171
|
+
const lower = trimmed.toLowerCase();
|
|
172
|
+
// The default Agent slug maps to the Spark X2 service in the supplied price list.
|
|
173
|
+
if (lower === "xsparkx2agent") return "xsparkx2";
|
|
174
|
+
return trimmed;
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
// Unsloth Studio can mix local engines and paid API providers in one database.
|
|
168
178
|
// The parser qualifies metered models with their provider and marks local,
|
|
169
179
|
// subscription-backed, or ambiguous custom routes as unpriced. Returning a
|
|
@@ -182,6 +192,7 @@ function normalizeUnslothModel(model) {
|
|
|
182
192
|
// raw model name is preserved for storage/display). Add a source here when its
|
|
183
193
|
// model strings don't match the LiteLLM/curated keys verbatim.
|
|
184
194
|
const SOURCE_MODEL_NORMALIZERS = {
|
|
195
|
+
acode: normalizeIFlytekMaasModel,
|
|
185
196
|
antigravity: normalizeAntigravityModel,
|
|
186
197
|
claude: normalizeClaudeModel,
|
|
187
198
|
cursor: normalizeCursorModel,
|
|
@@ -242,6 +253,22 @@ function lookupPricing(model, { curated, litellm, source } = {}) {
|
|
|
242
253
|
const lower = lookupModel.toLowerCase();
|
|
243
254
|
const dotForm = buildDotRestoredModel(lookupModel);
|
|
244
255
|
|
|
256
|
+
// 0. CURATED source exact. Source-specific prices apply only to their source,
|
|
257
|
+
// preventing collisions with public prices for same-named models from other CLIs.
|
|
258
|
+
const sourceKey = typeof source === "string" ? source.toLowerCase() : "";
|
|
259
|
+
// AStudio does not disclose its routed model. Stop before generic aliases
|
|
260
|
+
// and fuzzy matching can turn an unresolved router into a priced model.
|
|
261
|
+
if (sourceKey === "acode" && (lower === "auto" || lower.endsWith("-auto"))) {
|
|
262
|
+
return { hit: false, source: "miss", value: null };
|
|
263
|
+
}
|
|
264
|
+
const sourceExact = curated.source_exact?.[sourceKey];
|
|
265
|
+
if (sourceExact) {
|
|
266
|
+
const sourceValue = lookupExactCaseInsensitive(sourceExact, lookupModel);
|
|
267
|
+
if (sourceValue) {
|
|
268
|
+
return { hit: true, source: "curated:source-exact", value: sourceValue };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
245
272
|
// 1. CURATED exact
|
|
246
273
|
if (curated.exact && curated.exact[lookupModel]) {
|
|
247
274
|
return { hit: true, source: "curated:exact", value: curated.exact[lookupModel] };
|
|
@@ -382,6 +409,7 @@ module.exports = {
|
|
|
382
409
|
lookupPricing,
|
|
383
410
|
stripReasoningSuffix,
|
|
384
411
|
normalizeAntigravityModel,
|
|
412
|
+
normalizeIFlytekMaasModel,
|
|
385
413
|
normalizeClaudeModel,
|
|
386
414
|
normalizeCursorModel,
|
|
387
415
|
normalizeZedModel,
|