agent-usage-all-in-one 0.9.0 → 0.10.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.md +10 -17
- package/README.zh-CN.md +4 -6
- package/dist/cli.js +264 -94
- package/dist/cli.js.map +1 -1
- package/dist/web/_app/immutable/chunks/B_xPTqFX.js +1 -0
- package/dist/web/_app/immutable/entry/{app.BroDwe18.js → app.DSyJ5FKE.js} +2 -2
- package/dist/web/_app/immutable/entry/start.8CrPPG1u.js +1 -0
- package/dist/web/_app/immutable/nodes/{1.BM7JX0do.js → 1.Cu2ljShp.js} +1 -1
- package/dist/web/_app/immutable/nodes/2.DGyi6mL2.js +27 -0
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +6 -6
- package/package.json +1 -1
- package/dist/web/_app/immutable/chunks/BJnEwO-n.js +0 -1
- package/dist/web/_app/immutable/entry/start.IgKfiouf.js +0 -1
- package/dist/web/_app/immutable/nodes/2.CE6LF_M_.js +0 -26
package/README.md
CHANGED
|
@@ -17,8 +17,6 @@ It offers advice but never switches agents automatically.
|
|
|
17
17
|
|
|
18
18
|
## Compared
|
|
19
19
|
|
|
20
|
-

|
|
21
|
-
|
|
22
20
|
Honest category axes and when to prefer Agent Usage vs provider UIs, exporters, or FinOps dashboards: [docs/comparison.md](docs/comparison.md).
|
|
23
21
|
|
|
24
22
|
Sourced named competitors (ccusage, CodeBurn, Token Monitor, TokenTracker): [docs/named-competitors.md](docs/named-competitors.md).
|
|
@@ -35,7 +33,8 @@ The dashboard has two primary tabs:
|
|
|
35
33
|
selected window, metric, and currency sit in the same sticky header as the
|
|
36
34
|
primary tabs. The summary board leads with the headline amount beside a
|
|
37
35
|
GitHub-style last-year usage wall of headline-included recorded Tokens, then
|
|
38
|
-
charts
|
|
36
|
+
charts each known model separately by agent and billing domain on the share
|
|
37
|
+
pie and the interactive daily trend, and shows model
|
|
39
38
|
rankings with visual share bars and the public API retail equivalent of
|
|
40
39
|
eligible token usage. The year wall is independent of the selected 24-hour,
|
|
41
40
|
7-day, or 30-day window.
|
|
@@ -51,8 +50,6 @@ unclassified or unpriced instead of being guessed or displayed as zero.
|
|
|
51
50
|
Grok Build/SuperGrok and xAI API are independent billing domains. Their quotas,
|
|
52
51
|
tokens, and costs are never added together.
|
|
53
52
|
|
|
54
|
-

|
|
55
|
-
|
|
56
53
|
## Fast, progressive startup
|
|
57
54
|
|
|
58
55
|
The loopback web service starts before connector discovery or data processing.
|
|
@@ -60,11 +57,13 @@ Cached results are available immediately while discovery, provider usage, model
|
|
|
60
57
|
pricing, and retention run as independent background modules. Each dashboard tab
|
|
61
58
|
shows only its own update indicator and completed sections remain usable.
|
|
62
59
|
|
|
63
|
-
Transcript scans use a persistent, path-redacted file index.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
Transcript scans use a persistent, path-redacted file index. Refresh derives API
|
|
61
|
+
retail equivalent only for new or changed Token observations. Historical retail
|
|
62
|
+
pricing backfills remaining unpriced observations when the pricing catalog
|
|
63
|
+
version changes. SQLite time/provider/model indexes and retention compaction run
|
|
64
|
+
in a worker after provider collection only when those jobs have work. Price
|
|
65
|
+
backfill processes bounded pages. Token and cost refresh bars follow price
|
|
66
|
+
derivation, not connector collection or retention. Settings includes an explicitly confirmed
|
|
68
67
|
**Hard rebuild all data** action for troubleshooting. It ignores these caches, can
|
|
69
68
|
use substantial resources, and may take a long time without blocking the web UI.
|
|
70
69
|
|
|
@@ -89,8 +88,6 @@ oversight — see [ADR 017](docs/adr/017-macos-node24-npm-runtime.md) and the
|
|
|
89
88
|
[platform roadmap](docs/platform-roadmap.md). Linux and Homebrew / DMG are not
|
|
90
89
|
shipped.
|
|
91
90
|
|
|
92
|
-

|
|
93
|
-
|
|
94
91
|
```bash
|
|
95
92
|
npx agent-usage-all-in-one
|
|
96
93
|
```
|
|
@@ -148,9 +145,7 @@ evidence remain visibly distinct.
|
|
|
148
145
|
|
|
149
146
|
## First launch / empty state
|
|
150
147
|
|
|
151
|
-
Until connectors find local client data, the dashboard shows an honest empty state — connect or use an installed agent first
|
|
152
|
-
|
|
153
|
-

|
|
148
|
+
Until connectors find local client data, the dashboard shows an honest empty state — connect or use an installed agent first.
|
|
154
149
|
|
|
155
150
|
## Credentials and privacy
|
|
156
151
|
|
|
@@ -188,8 +183,6 @@ Linux, Homebrew, and DMG stay off the product until the conditions in
|
|
|
188
183
|
|
|
189
184
|
> **VISION only — not shipped.** Menu bar / always-available glance is tracked in [issue #19](https://github.com/Blackman99/agent-usage-all-in-one/issues/19). Do not treat the mock as a current feature.
|
|
190
185
|
|
|
191
|
-

|
|
192
|
-
|
|
193
186
|
## License and community
|
|
194
187
|
|
|
195
188
|
MIT — see [LICENSE](LICENSE). Also see [CONTRIBUTING.md](CONTRIBUTING.md),
|
package/README.zh-CN.md
CHANGED
|
@@ -16,8 +16,6 @@ API 对等费用、历史与诊断。它只提供建议,不会自动切换 Age
|
|
|
16
16
|
|
|
17
17
|
## 对比说明
|
|
18
18
|
|
|
19
|
-

|
|
20
|
-
|
|
21
19
|
按类别对比何时更适合用 Agent Usage,而非各平台官网、导出工具或通用 FinOps 看板:[docs/comparison.md](docs/comparison.md)。
|
|
22
20
|
|
|
23
21
|
具名竞品对照(ccusage、CodeBurn、Token Monitor、TokenTracker):[docs/named-competitors.md](docs/named-competitors.md)。
|
|
@@ -29,7 +27,7 @@ API 对等费用、历史与诊断。它只提供建议,不会自动切换 Age
|
|
|
29
27
|
页面只有两个主要标签页:
|
|
30
28
|
|
|
31
29
|
- **Agent 用量**:保留各平台原生的 5 小时、周、月、All models 和 Fable only 等额度标签与刷新时间。
|
|
32
|
-
- **Token 与模型费用**:支持 24 小时、7 天和 30 天范围。所选窗口、指标与币种和主标签页共用同一条吸顶导航。汇总卡片左侧是金额或 Token 头条,右侧是过去一年的 GitHub 风格用量墙(只统计计入摘要的已记录 Token
|
|
30
|
+
- **Token 与模型费用**:支持 24 小时、7 天和 30 天范围。所选窗口、指标与币种和主标签页共用同一条吸顶导航。汇总卡片左侧是金额或 Token 头条,右侧是过去一年的 GitHub 风格用量墙(只统计计入摘要的已记录 Token),随后用图表按 agent、计费域和模型分开展示占比与可交互的每日趋势,再以占比条呈现模型排行及符合条件的 Token 按公开 API 价格计算出的对等费用。用量墙与所选的 24 小时 / 7 天 / 30 天窗口相互独立。
|
|
33
31
|
- **设置面板**:采用居中双栏结构,将连接管理、自定义模型费率、运行监控、系统诊断与数据隐私划分为 5 个独立分类,方便高效维护各项配置。
|
|
34
32
|
|
|
35
33
|
实际账单、平台报告估算、固定订阅费和 API 对等零售价是四类互不混合的证据。
|
|
@@ -43,9 +41,9 @@ Grok Build/SuperGrok 与 xAI API 是两个独立计费域,其额度、Token
|
|
|
43
41
|
本地 Web 服务会先启动,再在后台运行连接发现、平台用量、模型定价和保留整理四个独立模块。
|
|
44
42
|
已有缓存会立即展示;每个标签页只在自己的数据区域显示更新状态,已经完成的内容始终可用。
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
本地会话记录扫描使用跨进程持久化且不保存原始个人路径的文件索引。刷新只为新增或定价输入已变化的
|
|
45
|
+
Token 观测计算 API 对等零售价;历史费用只在价格目录版本变化时补算仍未定价的观测,并按有限批次处理。
|
|
46
|
+
时间、平台、模型和计费域索引及保留期整理会在平台采集完成后交给后台工作线程,且仅在确有工作(索引尚未建立、或存在超过 90 天的原始观测)时执行。Token 与费用区域的刷新条只在价格推导实际运行时显示,不等待连接器采集或保留整理。设置里提供需要明确确认的
|
|
49
47
|
**硬重算全部数据**:它会忽略缓存、消耗较多资源,并且可能等待很久,但不会阻塞页面。
|
|
50
48
|
|
|
51
49
|
## 开发调试
|
package/dist/cli.js
CHANGED
|
@@ -789,17 +789,44 @@ function mergeCatalogWithCustomRates(catalog, customRates) {
|
|
|
789
789
|
entries: [...customEntries, ...catalog.entries]
|
|
790
790
|
};
|
|
791
791
|
}
|
|
792
|
+
function catalogModelKey(providerId, model) {
|
|
793
|
+
return `${providerId}\0${model.trim().toLowerCase()}`;
|
|
794
|
+
}
|
|
795
|
+
function indexRetailPriceCatalog(catalog) {
|
|
796
|
+
const cached = catalogIndexes.get(catalog);
|
|
797
|
+
if (cached) return cached;
|
|
798
|
+
const byProviderAndModel = /* @__PURE__ */ new Map();
|
|
799
|
+
for (const entry of catalog.entries) {
|
|
800
|
+
const models = /* @__PURE__ */ new Set([entry.canonicalModel, ...entry.aliases]);
|
|
801
|
+
for (const model of models) {
|
|
802
|
+
const key = catalogModelKey(entry.providerId, model);
|
|
803
|
+
const list = byProviderAndModel.get(key);
|
|
804
|
+
if (list) list.push(entry);
|
|
805
|
+
else byProviderAndModel.set(key, [entry]);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const index = { byProviderAndModel };
|
|
809
|
+
catalogIndexes.set(catalog, index);
|
|
810
|
+
return index;
|
|
811
|
+
}
|
|
812
|
+
function matchingCatalogEntries(index, providerId, observation, normalizedModel) {
|
|
813
|
+
const entries = index.byProviderAndModel.get(catalogModelKey(providerId, normalizedModel)) ?? [];
|
|
814
|
+
return entries.filter(
|
|
815
|
+
(entry) => entry.billingDomainId === observation.billingDomainId || entry.billingDomainId === "*"
|
|
816
|
+
);
|
|
817
|
+
}
|
|
792
818
|
function deriveRetailEquivalentCosts(snapshot2, catalog = ANTHROPIC_PRICING_CATALOG, calculatedAt = snapshot2.observedAt) {
|
|
793
819
|
const costs = [];
|
|
794
820
|
const decisions = [];
|
|
821
|
+
const index = indexRetailPriceCatalog(catalog);
|
|
795
822
|
for (const observation of snapshot2.usage) {
|
|
796
|
-
const result = priceObservation(snapshot2.provider.id, observation,
|
|
823
|
+
const result = priceObservation(snapshot2.provider.id, observation, index, calculatedAt);
|
|
797
824
|
decisions.push(result.decision);
|
|
798
825
|
if (result.cost) costs.push(result.cost);
|
|
799
826
|
}
|
|
800
827
|
return { costs, decisions };
|
|
801
828
|
}
|
|
802
|
-
function priceObservation(providerId, observation,
|
|
829
|
+
function priceObservation(providerId, observation, index, calculatedAt) {
|
|
803
830
|
const unavailable = (reason) => ({
|
|
804
831
|
cost: null,
|
|
805
832
|
decision: {
|
|
@@ -814,10 +841,11 @@ function priceObservation(providerId, observation, catalog, calculatedAt) {
|
|
|
814
841
|
return unavailable("model-unclassified");
|
|
815
842
|
}
|
|
816
843
|
const normalizedModel = normalized.model.trim().toLowerCase();
|
|
817
|
-
const matchingCandidates =
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
844
|
+
const matchingCandidates = matchingCatalogEntries(
|
|
845
|
+
index,
|
|
846
|
+
providerId,
|
|
847
|
+
observation,
|
|
848
|
+
normalizedModel
|
|
821
849
|
);
|
|
822
850
|
let selectedEntries;
|
|
823
851
|
const customExact = matchingCandidates.filter(
|
|
@@ -977,7 +1005,7 @@ function matchContext(rule, observation) {
|
|
|
977
1005
|
function preciseMoney(value) {
|
|
978
1006
|
return Number(value.toFixed(12));
|
|
979
1007
|
}
|
|
980
|
-
var ANTHROPIC_PRICING_CATALOG, OPENCODE_GO_SOURCE, XAI_PRICING_SOURCE, OPENAI_GPT_56_SOURCE, OPENAI_GPT_6_ASTRA_SOURCE, OPEN_CODE_GO_SOURCE_PATH, openCodeHistory, OPEN_CODE_BASE_HISTORY, OPEN_CODE_PRICE_HISTORY, OPEN_CODE_FLAT_MODELS, OPEN_CODE_CONTEXT_MODELS, DEEPSEEK_GO_MODELS, WEEKDAY_PEAK_RANGES, DEEPSEEK_OFFICIAL_SOURCE, DEEPSEEK_OFFICIAL_MODELS, DEEPSEEK_OFFICIAL_EFFECTIVE_FROM, GOOGLE_ANTIGRAVITY_SOURCE, GOOGLE_ANTIGRAVITY_EFFECTIVE_FROM, DIRECT_OFFICIAL_PRICING_CATALOG, OFFICIAL_PRICING_CATALOG;
|
|
1008
|
+
var ANTHROPIC_PRICING_CATALOG, OPENCODE_GO_SOURCE, XAI_PRICING_SOURCE, OPENAI_GPT_56_SOURCE, OPENAI_GPT_6_ASTRA_SOURCE, OPEN_CODE_GO_SOURCE_PATH, openCodeHistory, OPEN_CODE_BASE_HISTORY, OPEN_CODE_PRICE_HISTORY, OPEN_CODE_FLAT_MODELS, OPEN_CODE_CONTEXT_MODELS, DEEPSEEK_GO_MODELS, WEEKDAY_PEAK_RANGES, DEEPSEEK_OFFICIAL_SOURCE, DEEPSEEK_OFFICIAL_MODELS, DEEPSEEK_OFFICIAL_EFFECTIVE_FROM, GOOGLE_ANTIGRAVITY_SOURCE, GOOGLE_ANTIGRAVITY_EFFECTIVE_FROM, DIRECT_OFFICIAL_PRICING_CATALOG, OFFICIAL_PRICING_CATALOG, catalogIndexes;
|
|
981
1009
|
var init_retail_pricing = __esm({
|
|
982
1010
|
"src/core/retail-pricing.ts"() {
|
|
983
1011
|
"use strict";
|
|
@@ -1447,6 +1475,7 @@ var init_retail_pricing = __esm({
|
|
|
1447
1475
|
...openCodeLocalHistoryEntries(DIRECT_OFFICIAL_PRICING_CATALOG.entries)
|
|
1448
1476
|
]
|
|
1449
1477
|
};
|
|
1478
|
+
catalogIndexes = /* @__PURE__ */ new WeakMap();
|
|
1450
1479
|
}
|
|
1451
1480
|
});
|
|
1452
1481
|
|
|
@@ -1492,7 +1521,7 @@ function yieldToEventLoop() {
|
|
|
1492
1521
|
}
|
|
1493
1522
|
function yieldForPendingHttp() {
|
|
1494
1523
|
return new Promise((resolve5) => {
|
|
1495
|
-
setTimeout(resolve5, PENDING_HTTP_YIELD_MS);
|
|
1524
|
+
setTimeout(() => setImmediate(resolve5), PENDING_HTTP_YIELD_MS);
|
|
1496
1525
|
});
|
|
1497
1526
|
}
|
|
1498
1527
|
function providerForConnector(id, definitions) {
|
|
@@ -1639,23 +1668,16 @@ var init_usage_application = __esm({
|
|
|
1639
1668
|
let cursor = null;
|
|
1640
1669
|
do {
|
|
1641
1670
|
const page = this.#repository.getRetailPricingBackfillPage(cursor, 250);
|
|
1642
|
-
|
|
1643
|
-
const costs = deriveRetailEquivalentCosts(
|
|
1644
|
-
snapshot2,
|
|
1645
|
-
this.#priceCatalog,
|
|
1646
|
-
calculatedAt
|
|
1647
|
-
).costs;
|
|
1648
|
-
if (costs.length > 0) this.#repository.saveDerivedCosts(snapshot2.provider.id, costs);
|
|
1649
|
-
}
|
|
1671
|
+
this.#saveDerivedRetailCosts(page.snapshots, calculatedAt);
|
|
1650
1672
|
cursor = page.nextCursor;
|
|
1651
1673
|
if (cursor) await yieldToEventLoop();
|
|
1652
1674
|
} while (cursor);
|
|
1653
1675
|
} else {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1676
|
+
this.#saveDerivedRetailCosts(
|
|
1677
|
+
this.#repository.getRetailPricingBackfillSnapshots?.() ?? [],
|
|
1678
|
+
calculatedAt
|
|
1679
|
+
);
|
|
1680
|
+
await yieldToEventLoop();
|
|
1659
1681
|
}
|
|
1660
1682
|
this.#repository.saveApplicationState?.(catalogVersionKey, this.#priceCatalog.version);
|
|
1661
1683
|
}
|
|
@@ -2195,9 +2217,23 @@ var init_usage_application = __esm({
|
|
|
2195
2217
|
);
|
|
2196
2218
|
return result;
|
|
2197
2219
|
}
|
|
2220
|
+
#saveDerivedRetailCosts(snapshots, calculatedAt) {
|
|
2221
|
+
if (!this.#priceCatalog) return;
|
|
2222
|
+
for (const snapshot2 of snapshots) {
|
|
2223
|
+
const costs = deriveRetailEquivalentCosts(snapshot2, this.#priceCatalog, calculatedAt).costs;
|
|
2224
|
+
if (costs.length > 0) this.#repository.saveDerivedCosts?.(snapshot2.provider.id, costs);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2198
2227
|
#withRetailCosts(snapshot2) {
|
|
2199
|
-
|
|
2200
|
-
|
|
2228
|
+
if (!this.#priceCatalog) return snapshot2;
|
|
2229
|
+
const usage = this.#repository.observationsNeedingRetailDerivation ? this.#repository.observationsNeedingRetailDerivation(snapshot2.provider.id, snapshot2.usage) : snapshot2.usage;
|
|
2230
|
+
if (usage.length === 0) return snapshot2;
|
|
2231
|
+
const retailCosts = deriveRetailEquivalentCosts(
|
|
2232
|
+
{ ...snapshot2, usage },
|
|
2233
|
+
this.#priceCatalog,
|
|
2234
|
+
this.#clock().toISOString()
|
|
2235
|
+
).costs;
|
|
2236
|
+
return retailCosts.length === 0 ? snapshot2 : { ...snapshot2, costs: [...snapshot2.costs, ...retailCosts] };
|
|
2201
2237
|
}
|
|
2202
2238
|
async discoverConnectors() {
|
|
2203
2239
|
if (!this.#discoveryProbe) return this.getConnectorStatuses();
|
|
@@ -2300,7 +2336,7 @@ var init_usage_application = __esm({
|
|
|
2300
2336
|
return status;
|
|
2301
2337
|
}
|
|
2302
2338
|
};
|
|
2303
|
-
PENDING_HTTP_YIELD_MS =
|
|
2339
|
+
PENDING_HTTP_YIELD_MS = 75;
|
|
2304
2340
|
}
|
|
2305
2341
|
});
|
|
2306
2342
|
|
|
@@ -6006,7 +6042,6 @@ var init_antigravity_sqlite_usage_client = __esm({
|
|
|
6006
6042
|
"src/server/antigravity-sqlite-usage-client.ts"() {
|
|
6007
6043
|
"use strict";
|
|
6008
6044
|
init_token_normalization();
|
|
6009
|
-
init_retail_pricing();
|
|
6010
6045
|
({ DatabaseSync: DatabaseSync2 } = createRequire2(import.meta.url)(
|
|
6011
6046
|
"node:sqlite"
|
|
6012
6047
|
));
|
|
@@ -6015,7 +6050,6 @@ var init_antigravity_sqlite_usage_client = __esm({
|
|
|
6015
6050
|
#clock;
|
|
6016
6051
|
#lookbackDays;
|
|
6017
6052
|
#cachePath;
|
|
6018
|
-
#priceCatalog;
|
|
6019
6053
|
#fileCache = /* @__PURE__ */ new Map();
|
|
6020
6054
|
#cacheLoaded = false;
|
|
6021
6055
|
constructor(options) {
|
|
@@ -6023,7 +6057,6 @@ var init_antigravity_sqlite_usage_client = __esm({
|
|
|
6023
6057
|
this.#clock = options.clock ?? (() => /* @__PURE__ */ new Date());
|
|
6024
6058
|
this.#lookbackDays = options.lookbackDays ?? 90;
|
|
6025
6059
|
this.#cachePath = options.cachePath;
|
|
6026
|
-
this.#priceCatalog = options.priceCatalog ?? OFFICIAL_PRICING_CATALOG;
|
|
6027
6060
|
}
|
|
6028
6061
|
async readUsage(options = { mode: "incremental" }) {
|
|
6029
6062
|
await this.#loadCache();
|
|
@@ -6048,21 +6081,10 @@ var init_antigravity_sqlite_usage_client = __esm({
|
|
|
6048
6081
|
}
|
|
6049
6082
|
}
|
|
6050
6083
|
usage.sort((a, b) => a.observedAt.localeCompare(b.observedAt) || a.id.localeCompare(b.id));
|
|
6051
|
-
const { costs } = deriveRetailEquivalentCosts(
|
|
6052
|
-
{
|
|
6053
|
-
provider: { id: "antigravity", displayName: "Antigravity" },
|
|
6054
|
-
billingDomains: [{ id: "code-assist-subscription", displayName: "Gemini Code Assist" }],
|
|
6055
|
-
quotaBuckets: [],
|
|
6056
|
-
usage,
|
|
6057
|
-
costs: [],
|
|
6058
|
-
observedAt: this.#clock().toISOString()
|
|
6059
|
-
},
|
|
6060
|
-
this.#priceCatalog
|
|
6061
|
-
);
|
|
6062
6084
|
await this.#persistCache();
|
|
6063
6085
|
return {
|
|
6064
6086
|
usage,
|
|
6065
|
-
costs,
|
|
6087
|
+
costs: [],
|
|
6066
6088
|
complete: allComplete
|
|
6067
6089
|
};
|
|
6068
6090
|
}
|
|
@@ -8120,6 +8142,23 @@ function pricingBackfillSnapshots(rows) {
|
|
|
8120
8142
|
}
|
|
8121
8143
|
return [...snapshots.values()];
|
|
8122
8144
|
}
|
|
8145
|
+
function unpricedRetailPredicate(alias) {
|
|
8146
|
+
return `NOT EXISTS (
|
|
8147
|
+
SELECT 1 FROM cost_records priced
|
|
8148
|
+
WHERE priced.provider_id = ${alias}.provider_id
|
|
8149
|
+
AND priced.usage_observation_id = ${alias}.id
|
|
8150
|
+
AND priced.kind = 'retail-equivalent'
|
|
8151
|
+
)`;
|
|
8152
|
+
}
|
|
8153
|
+
function chunked(items, size) {
|
|
8154
|
+
if (items.length === 0) return [];
|
|
8155
|
+
if (items.length <= size) return [items];
|
|
8156
|
+
const chunks = [];
|
|
8157
|
+
for (let index = 0; index < items.length; index += size) {
|
|
8158
|
+
chunks.push(items.slice(index, index + size));
|
|
8159
|
+
}
|
|
8160
|
+
return chunks;
|
|
8161
|
+
}
|
|
8123
8162
|
function normalizeCostForPersistence(cost) {
|
|
8124
8163
|
if (cost.kind !== "subscription") return cost;
|
|
8125
8164
|
return {
|
|
@@ -8473,50 +8512,17 @@ function buildTokenMoneyWorkbench(providers, now, query) {
|
|
|
8473
8512
|
};
|
|
8474
8513
|
});
|
|
8475
8514
|
const buckets = emptyIntervals.map((emptyInterval, index) => {
|
|
8476
|
-
const segments = allHistories.flatMap(
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
)
|
|
8486
|
-
|
|
8487
|
-
interval.costs,
|
|
8488
|
-
"reported-estimate",
|
|
8489
|
-
comparisonCurrency,
|
|
8490
|
-
interval.tokenEvidence.recordedTokens,
|
|
8491
|
-
history.exchangeRates
|
|
8492
|
-
);
|
|
8493
|
-
if (interval.tokenEvidence.observationCount === 0 && retailEquivalent2.records === 0 && reportedEstimate2.records === 0)
|
|
8494
|
-
return [];
|
|
8495
|
-
return [
|
|
8496
|
-
{
|
|
8497
|
-
providerId: provider.id,
|
|
8498
|
-
providerDisplayName: provider.displayName,
|
|
8499
|
-
billingDomainId: domain.id,
|
|
8500
|
-
billingDomainDisplayName: domain.displayName,
|
|
8501
|
-
includedInHeadline,
|
|
8502
|
-
recordedTokens: interval.tokenEvidence.recordedTokens,
|
|
8503
|
-
observationCount: interval.tokenEvidence.observationCount,
|
|
8504
|
-
timePrecisions: interval.tokenEvidence.timePrecisions,
|
|
8505
|
-
authorities: interval.authorities ?? [],
|
|
8506
|
-
lastObservedAt: interval.lastObservedAt ?? null,
|
|
8507
|
-
retailEquivalent: {
|
|
8508
|
-
status: retailEquivalent2.status,
|
|
8509
|
-
amount: retailEquivalent2.amount,
|
|
8510
|
-
currency: comparisonCurrency
|
|
8511
|
-
},
|
|
8512
|
-
reportedEstimate: {
|
|
8513
|
-
status: reportedEstimate2.status,
|
|
8514
|
-
amount: reportedEstimate2.amount,
|
|
8515
|
-
currency: comparisonCurrency
|
|
8516
|
-
}
|
|
8517
|
-
}
|
|
8518
|
-
];
|
|
8519
|
-
});
|
|
8515
|
+
const segments = allHistories.flatMap(
|
|
8516
|
+
({ provider, domain, history, includedInHeadline }) => workbenchTrendSegmentsForInterval(
|
|
8517
|
+
provider,
|
|
8518
|
+
domain,
|
|
8519
|
+
history,
|
|
8520
|
+
includedInHeadline,
|
|
8521
|
+
emptyInterval,
|
|
8522
|
+
index,
|
|
8523
|
+
comparisonCurrency
|
|
8524
|
+
)
|
|
8525
|
+
);
|
|
8520
8526
|
return {
|
|
8521
8527
|
start: emptyInterval.start.toISOString(),
|
|
8522
8528
|
end: emptyInterval.end.toISOString(),
|
|
@@ -8554,6 +8560,133 @@ function buildTokenMoneyWorkbench(providers, now, query) {
|
|
|
8554
8560
|
)
|
|
8555
8561
|
};
|
|
8556
8562
|
}
|
|
8563
|
+
function workbenchTrendSegmentsForInterval(provider, domain, history, includedInHeadline, emptyInterval, index, comparisonCurrency) {
|
|
8564
|
+
const interval = history.intervals[index];
|
|
8565
|
+
if (!interval) return [];
|
|
8566
|
+
const start = emptyInterval.start.toISOString();
|
|
8567
|
+
const end = emptyInterval.end.toISOString();
|
|
8568
|
+
const models = history.models.flatMap((model) => {
|
|
8569
|
+
const observations = (model.observations ?? []).filter(
|
|
8570
|
+
(observation) => observation.observedAt >= start && observation.observedAt < end
|
|
8571
|
+
);
|
|
8572
|
+
const priceEvidence = (model.priceEvidence ?? []).filter(
|
|
8573
|
+
(cost) => (cost.observedAt ?? "") >= start && (cost.observedAt ?? "") < end
|
|
8574
|
+
);
|
|
8575
|
+
const tokenEvidence = emptyTokenEvidence();
|
|
8576
|
+
const authorities = /* @__PURE__ */ new Set();
|
|
8577
|
+
let lastObservedAt = null;
|
|
8578
|
+
for (const observation of observations) {
|
|
8579
|
+
tokenEvidence.recordedTokens += observation.recordedTokens;
|
|
8580
|
+
tokenEvidence.observationCount += 1;
|
|
8581
|
+
tokenEvidence.timePrecisions.add(observation.timePrecision);
|
|
8582
|
+
authorities.add(observation.authority);
|
|
8583
|
+
if (!lastObservedAt || observation.observedAt > lastObservedAt) {
|
|
8584
|
+
lastObservedAt = observation.observedAt;
|
|
8585
|
+
}
|
|
8586
|
+
}
|
|
8587
|
+
const retailEquivalent = buildWorkbenchMetric(
|
|
8588
|
+
priceEvidence,
|
|
8589
|
+
"retail-equivalent",
|
|
8590
|
+
comparisonCurrency,
|
|
8591
|
+
tokenEvidence.recordedTokens,
|
|
8592
|
+
history.exchangeRates
|
|
8593
|
+
);
|
|
8594
|
+
const reportedEstimate = buildWorkbenchMetric(
|
|
8595
|
+
priceEvidence,
|
|
8596
|
+
"reported-estimate",
|
|
8597
|
+
comparisonCurrency,
|
|
8598
|
+
tokenEvidence.recordedTokens,
|
|
8599
|
+
history.exchangeRates
|
|
8600
|
+
);
|
|
8601
|
+
if (tokenEvidence.observationCount === 0 && retailEquivalent.records === 0 && reportedEstimate.records === 0) {
|
|
8602
|
+
return [];
|
|
8603
|
+
}
|
|
8604
|
+
return [
|
|
8605
|
+
{
|
|
8606
|
+
providerId: provider.id,
|
|
8607
|
+
providerDisplayName: provider.displayName,
|
|
8608
|
+
billingDomainId: domain.id,
|
|
8609
|
+
billingDomainDisplayName: domain.displayName,
|
|
8610
|
+
model: model.model,
|
|
8611
|
+
includedInHeadline,
|
|
8612
|
+
recordedTokens: tokenEvidence.recordedTokens,
|
|
8613
|
+
observationCount: tokenEvidence.observationCount,
|
|
8614
|
+
timePrecisions: [...tokenEvidence.timePrecisions].sort(),
|
|
8615
|
+
authorities: [...authorities].sort(),
|
|
8616
|
+
lastObservedAt,
|
|
8617
|
+
retailEquivalent: {
|
|
8618
|
+
status: retailEquivalent.status,
|
|
8619
|
+
amount: retailEquivalent.amount,
|
|
8620
|
+
currency: comparisonCurrency
|
|
8621
|
+
},
|
|
8622
|
+
reportedEstimate: {
|
|
8623
|
+
status: reportedEstimate.status,
|
|
8624
|
+
amount: reportedEstimate.amount,
|
|
8625
|
+
currency: comparisonCurrency
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
];
|
|
8629
|
+
});
|
|
8630
|
+
const unclassifiedObservations = (history.unclassified.observations ?? []).filter(
|
|
8631
|
+
(observation) => observation.observedAt >= start && observation.observedAt < end
|
|
8632
|
+
);
|
|
8633
|
+
const unclassifiedTokenEvidence = emptyTokenEvidence();
|
|
8634
|
+
const unclassifiedAuthorities = /* @__PURE__ */ new Set();
|
|
8635
|
+
let unclassifiedLastObservedAt = null;
|
|
8636
|
+
for (const observation of unclassifiedObservations) {
|
|
8637
|
+
unclassifiedTokenEvidence.recordedTokens += observation.recordedTokens;
|
|
8638
|
+
unclassifiedTokenEvidence.observationCount += 1;
|
|
8639
|
+
unclassifiedTokenEvidence.timePrecisions.add(observation.timePrecision);
|
|
8640
|
+
unclassifiedAuthorities.add(observation.authority);
|
|
8641
|
+
if (!unclassifiedLastObservedAt || observation.observedAt > unclassifiedLastObservedAt) {
|
|
8642
|
+
unclassifiedLastObservedAt = observation.observedAt;
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
const leftoverCosts = models.length === 0 ? interval.costs : [];
|
|
8646
|
+
const leftoverRetailEquivalent = buildWorkbenchMetric(
|
|
8647
|
+
leftoverCosts,
|
|
8648
|
+
"retail-equivalent",
|
|
8649
|
+
comparisonCurrency,
|
|
8650
|
+
unclassifiedTokenEvidence.recordedTokens,
|
|
8651
|
+
history.exchangeRates
|
|
8652
|
+
);
|
|
8653
|
+
const leftoverReportedEstimate = buildWorkbenchMetric(
|
|
8654
|
+
leftoverCosts,
|
|
8655
|
+
"reported-estimate",
|
|
8656
|
+
comparisonCurrency,
|
|
8657
|
+
unclassifiedTokenEvidence.recordedTokens,
|
|
8658
|
+
history.exchangeRates
|
|
8659
|
+
);
|
|
8660
|
+
if (unclassifiedTokenEvidence.observationCount === 0 && leftoverRetailEquivalent.records === 0 && leftoverReportedEstimate.records === 0) {
|
|
8661
|
+
return models;
|
|
8662
|
+
}
|
|
8663
|
+
return [
|
|
8664
|
+
...models,
|
|
8665
|
+
{
|
|
8666
|
+
providerId: provider.id,
|
|
8667
|
+
providerDisplayName: provider.displayName,
|
|
8668
|
+
billingDomainId: domain.id,
|
|
8669
|
+
billingDomainDisplayName: domain.displayName,
|
|
8670
|
+
model: null,
|
|
8671
|
+
includedInHeadline,
|
|
8672
|
+
recordedTokens: unclassifiedTokenEvidence.recordedTokens,
|
|
8673
|
+
observationCount: unclassifiedTokenEvidence.observationCount,
|
|
8674
|
+
timePrecisions: [...unclassifiedTokenEvidence.timePrecisions].sort(),
|
|
8675
|
+
authorities: [...unclassifiedAuthorities].sort(),
|
|
8676
|
+
lastObservedAt: unclassifiedLastObservedAt,
|
|
8677
|
+
retailEquivalent: {
|
|
8678
|
+
status: leftoverRetailEquivalent.status,
|
|
8679
|
+
amount: leftoverRetailEquivalent.amount,
|
|
8680
|
+
currency: comparisonCurrency
|
|
8681
|
+
},
|
|
8682
|
+
reportedEstimate: {
|
|
8683
|
+
status: leftoverReportedEstimate.status,
|
|
8684
|
+
amount: leftoverReportedEstimate.amount,
|
|
8685
|
+
currency: comparisonCurrency
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
];
|
|
8689
|
+
}
|
|
8557
8690
|
function allDomainHistories(providers) {
|
|
8558
8691
|
return providers.flatMap(
|
|
8559
8692
|
(provider) => provider.billingDomains.map((domain) => ({
|
|
@@ -9342,13 +9475,18 @@ function freshnessStatus(lastSuccessAt, now) {
|
|
|
9342
9475
|
if (!lastSuccessAt) return "unavailable";
|
|
9343
9476
|
return now.getTime() - new Date(lastSuccessAt).getTime() <= FRESHNESS_WINDOW_MS ? "fresh" : "stale";
|
|
9344
9477
|
}
|
|
9345
|
-
var FRESHNESS_WINDOW_MS, QUERY_INDEXES_SQL, DELETE_DERIVED_RETAIL_COSTS_SQL, DELETE_DEMO_PROVIDER_DATA_SQL, DatabaseSync3, RETENTION_AGGREGATE_SQL, SqliteUsageRepository;
|
|
9478
|
+
var FRESHNESS_WINDOW_MS, SQLITE_VARIABLE_CHUNK, USAGE_PRICING_INPUT_COLUMNS, QUERY_INDEXES_SQL, DELETE_DERIVED_RETAIL_COSTS_SQL, DELETE_DEMO_PROVIDER_DATA_SQL, DatabaseSync3, RETENTION_AGGREGATE_SQL, SqliteUsageRepository;
|
|
9346
9479
|
var init_sqlite_usage_repository = __esm({
|
|
9347
9480
|
"src/server/sqlite-usage-repository.ts"() {
|
|
9348
9481
|
"use strict";
|
|
9349
9482
|
init_token_normalization();
|
|
9350
9483
|
init_quota_normalization();
|
|
9351
9484
|
FRESHNESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
9485
|
+
SQLITE_VARIABLE_CHUNK = 400;
|
|
9486
|
+
USAGE_PRICING_INPUT_COLUMNS = `id, billing_domain_id, model, observed_at, total_tokens, input_tokens,
|
|
9487
|
+
output_tokens, reasoning_tokens, cache_read_tokens, cache_write_tokens, cache_write_5m_tokens,
|
|
9488
|
+
cache_write_1h_tokens, unclassified_tokens, reasoning_semantics, cache_read_semantics,
|
|
9489
|
+
cache_write_semantics, model_attribution, time_precision, aggregation_temporality`;
|
|
9352
9490
|
QUERY_INDEXES_SQL = `
|
|
9353
9491
|
CREATE INDEX IF NOT EXISTS usage_observed_at_idx
|
|
9354
9492
|
ON usage_observations(observed_at);
|
|
@@ -9627,14 +9765,9 @@ var init_sqlite_usage_repository = __esm({
|
|
|
9627
9765
|
usage_scope = excluded.usage_scope,
|
|
9628
9766
|
aggregation_temporality = excluded.aggregation_temporality`
|
|
9629
9767
|
);
|
|
9630
|
-
const
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
cache_write_1h_tokens, unclassified_tokens,
|
|
9634
|
-
reasoning_semantics, cache_read_semantics, cache_write_semantics, model_attribution,
|
|
9635
|
-
time_precision, aggregation_temporality
|
|
9636
|
-
FROM usage_observations
|
|
9637
|
-
WHERE provider_id = ? AND id = ?`
|
|
9768
|
+
const existingById = this.#usagePricingInputs(
|
|
9769
|
+
snapshot2.provider.id,
|
|
9770
|
+
snapshot2.usage.map((observation) => observation.id)
|
|
9638
9771
|
);
|
|
9639
9772
|
const deleteDerivedCostsStatement = this.#database.prepare(
|
|
9640
9773
|
`DELETE FROM cost_records
|
|
@@ -9642,7 +9775,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
9642
9775
|
);
|
|
9643
9776
|
for (const observation of snapshot2.usage) {
|
|
9644
9777
|
const normalized = normalizeTokenObservation(observation);
|
|
9645
|
-
const existing =
|
|
9778
|
+
const existing = existingById.get(normalized.id);
|
|
9646
9779
|
if (existing && pricingInputsChanged(existing, normalized)) {
|
|
9647
9780
|
deleteDerivedCostsStatement.run(snapshot2.provider.id, normalized.id);
|
|
9648
9781
|
}
|
|
@@ -9812,6 +9945,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
9812
9945
|
JOIN billing_domains b
|
|
9813
9946
|
ON b.provider_id = u.provider_id AND b.id = u.billing_domain_id
|
|
9814
9947
|
WHERE ${additiveUsagePredicate("u")}
|
|
9948
|
+
AND ${unpricedRetailPredicate("u")}
|
|
9815
9949
|
ORDER BY u.provider_id, u.observed_at, u.id`
|
|
9816
9950
|
).all();
|
|
9817
9951
|
return pricingBackfillSnapshots(rows);
|
|
@@ -9834,6 +9968,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
9834
9968
|
JOIN billing_domains b
|
|
9835
9969
|
ON b.provider_id = u.provider_id AND b.id = u.billing_domain_id
|
|
9836
9970
|
WHERE ${additiveUsagePredicate("u")}
|
|
9971
|
+
AND ${unpricedRetailPredicate("u")}
|
|
9837
9972
|
${cursorPredicate}
|
|
9838
9973
|
ORDER BY u.provider_id, u.observed_at, u.id
|
|
9839
9974
|
LIMIT ?`
|
|
@@ -9851,6 +9986,30 @@ var init_sqlite_usage_repository = __esm({
|
|
|
9851
9986
|
} : null
|
|
9852
9987
|
};
|
|
9853
9988
|
}
|
|
9989
|
+
observationsNeedingRetailDerivation(providerId, observations) {
|
|
9990
|
+
if (observations.length === 0) return [];
|
|
9991
|
+
const existing = this.#usagePricingInputs(
|
|
9992
|
+
providerId,
|
|
9993
|
+
observations.map((observation) => observation.id)
|
|
9994
|
+
);
|
|
9995
|
+
return observations.filter((observation) => {
|
|
9996
|
+
const normalized = normalizeTokenObservation(observation);
|
|
9997
|
+
const prior = existing.get(normalized.id);
|
|
9998
|
+
return !prior || pricingInputsChanged(prior, normalized);
|
|
9999
|
+
});
|
|
10000
|
+
}
|
|
10001
|
+
#usagePricingInputs(providerId, observationIds) {
|
|
10002
|
+
const byId = /* @__PURE__ */ new Map();
|
|
10003
|
+
for (const ids of chunked(observationIds, SQLITE_VARIABLE_CHUNK)) {
|
|
10004
|
+
const rows = this.#database.prepare(
|
|
10005
|
+
`SELECT ${USAGE_PRICING_INPUT_COLUMNS}
|
|
10006
|
+
FROM usage_observations
|
|
10007
|
+
WHERE provider_id = ? AND id IN (${ids.map(() => "?").join(", ")})`
|
|
10008
|
+
).all(providerId, ...ids);
|
|
10009
|
+
for (const row of rows) byId.set(String(row.id), row);
|
|
10010
|
+
}
|
|
10011
|
+
return byId;
|
|
10012
|
+
}
|
|
9854
10013
|
saveDerivedCosts(providerId, costs) {
|
|
9855
10014
|
if (costs.length === 0) return;
|
|
9856
10015
|
if (costs.some((cost) => cost.kind !== "retail-equivalent")) {
|
|
@@ -10333,6 +10492,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
10333
10492
|
}
|
|
10334
10493
|
compactUsageHistory(now) {
|
|
10335
10494
|
const cutoff = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3).toISOString();
|
|
10495
|
+
if (!this.#hasObservationsOlderThan(cutoff)) return this.getRetentionStatus();
|
|
10336
10496
|
this.#database.exec("BEGIN IMMEDIATE");
|
|
10337
10497
|
try {
|
|
10338
10498
|
this.#database.prepare(RETENTION_AGGREGATE_SQL).run(cutoff);
|
|
@@ -10405,6 +10565,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
10405
10565
|
).run(key, value);
|
|
10406
10566
|
}
|
|
10407
10567
|
async ensureQueryIndexes() {
|
|
10568
|
+
if (this.#queryIndexesReady()) return;
|
|
10408
10569
|
await runDatabaseWorker({
|
|
10409
10570
|
databasePath: this.#databasePath,
|
|
10410
10571
|
operation: "indexes",
|
|
@@ -10413,6 +10574,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
10413
10574
|
}
|
|
10414
10575
|
async maintainUsageHistory(now) {
|
|
10415
10576
|
const cutoff = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3).toISOString();
|
|
10577
|
+
if (!this.#hasObservationsOlderThan(cutoff)) return this.getRetentionStatus();
|
|
10416
10578
|
return runDatabaseWorker({
|
|
10417
10579
|
databasePath: this.#databasePath,
|
|
10418
10580
|
operation: "retention",
|
|
@@ -10422,6 +10584,14 @@ var init_sqlite_usage_repository = __esm({
|
|
|
10422
10584
|
now: now.toISOString()
|
|
10423
10585
|
});
|
|
10424
10586
|
}
|
|
10587
|
+
#queryIndexesReady() {
|
|
10588
|
+
const indexes = this.#database.prepare("PRAGMA index_list('usage_observations')").all();
|
|
10589
|
+
return indexes.some((index) => index.name === "usage_provider_model_time_idx");
|
|
10590
|
+
}
|
|
10591
|
+
#hasObservationsOlderThan(cutoff) {
|
|
10592
|
+
const row = this.#database.prepare("SELECT 1 AS present FROM usage_observations WHERE observed_at < ? LIMIT 1").get(cutoff);
|
|
10593
|
+
return row !== void 0;
|
|
10594
|
+
}
|
|
10425
10595
|
close() {
|
|
10426
10596
|
this.#database.close();
|
|
10427
10597
|
}
|
|
@@ -10852,7 +11022,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
10852
11022
|
models: [...byModel.entries()].map(([model, value]) => {
|
|
10853
11023
|
const observationIds = new Set(value.observations.map((observation) => observation.id));
|
|
10854
11024
|
const priceEvidence = costs.filter(
|
|
10855
|
-
(cost) => (cost.kind === "retail-equivalent" || cost.kind === "reported-estimate") && cost.usage_observation_id !== null && observationIds.has(cost.usage_observation_id)
|
|
11025
|
+
(cost) => (cost.kind === "retail-equivalent" || cost.kind === "reported-estimate") && (cost.usage_observation_id !== null && observationIds.has(cost.usage_observation_id) || cost.usage_observation_id === null && cost.model === model)
|
|
10856
11026
|
).map((cost) => {
|
|
10857
11027
|
const summarized = summarizeCosts([cost], Number(cost.priced_tokens ?? 0))[0];
|
|
10858
11028
|
return {
|