ccc-notifier 0.5.0 → 0.6.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 +129 -124
- package/dist/{budget-TBWVHUKA.js → budget-V7CCMJHF.js} +1 -1
- package/dist/{chunk-O34L3NSI.js → chunk-27SJELD2.js} +107 -22
- package/dist/{chunk-QWBQN4XY.js → chunk-4JSRGJCZ.js} +42 -18
- package/dist/chunk-6HTETN26.js +226 -0
- package/dist/{chunk-S3XKO7MY.js → chunk-CH34OJ7Q.js} +7 -3
- package/dist/{chunk-LVMKY6JB.js → chunk-D4D76RGQ.js} +27 -217
- package/dist/{chunk-5PH7PPD6.js → chunk-OOAC5ULQ.js} +25 -1
- package/dist/chunk-OYD6H3ZZ.js +124 -0
- package/dist/{chunk-DDUK4EWQ.js → chunk-T6Z2ZAN4.js} +1 -1
- package/dist/cli.js +26 -24
- package/dist/{dashboard-6F2QNUJT.js → dashboard-NORNHUPT.js} +3 -4
- package/dist/{history-7KN26LJH.js → history-DL4SG3PG.js} +3 -5
- package/dist/{mute-MPX3K7QN.js → mute-UIR5P5N5.js} +2 -2
- package/dist/{setup-IXPMVWOE.js → setup-3SRNQ5PG.js} +4 -3
- package/dist/{subagent-store-A6MIN22X.js → subagent-store-US4TJJQR.js} +1 -1
- package/dist/sweep-RUPPGAWJ.js +795 -0
- package/dist/{track-KEZON6KI.js → track-H4AIT575.js} +40 -28
- package/package.json +1 -1
- package/dist/chunk-ENGUOLTD.js +0 -92
- package/dist/chunk-O2AR4PXU.js +0 -82
- package/dist/sweep-UIONM4UA.js +0 -589
|
@@ -10,16 +10,14 @@ import {
|
|
|
10
10
|
} from "./chunk-J5QAYTFE.js";
|
|
11
11
|
import {
|
|
12
12
|
makeFullDashboardState,
|
|
13
|
+
waitForDataLock,
|
|
13
14
|
writeFullDashboardStateAtomic
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import {
|
|
16
|
-
waitForDataLock
|
|
17
|
-
} from "./chunk-O34L3NSI.js";
|
|
15
|
+
} from "./chunk-27SJELD2.js";
|
|
18
16
|
import {
|
|
19
17
|
paths,
|
|
20
18
|
readConfig,
|
|
21
19
|
readTurns
|
|
22
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OOAC5ULQ.js";
|
|
23
21
|
|
|
24
22
|
// src/dashboard.ts
|
|
25
23
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -128,6 +126,13 @@ function dateKeyOf(d) {
|
|
|
128
126
|
const day = String(d.getDate()).padStart(2, "0");
|
|
129
127
|
return `${y}-${m}-${day}`;
|
|
130
128
|
}
|
|
129
|
+
function inclusiveLocalDateSpan(min, max) {
|
|
130
|
+
const toUtcDay = (d) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate());
|
|
131
|
+
const minDay = toUtcDay(min);
|
|
132
|
+
const maxDay = toUtcDay(max);
|
|
133
|
+
if (!Number.isFinite(minDay) || !Number.isFinite(maxDay) || maxDay < minDay) return null;
|
|
134
|
+
return Math.floor((maxDay - minDay) / 864e5) + 1;
|
|
135
|
+
}
|
|
131
136
|
function fmtLocalDateTime(iso) {
|
|
132
137
|
const d = new Date(iso);
|
|
133
138
|
if (Number.isNaN(d.getTime())) return iso;
|
|
@@ -681,14 +686,28 @@ var APP_JS = `<script>
|
|
|
681
686
|
lastRenderedGran = GRAN;
|
|
682
687
|
}
|
|
683
688
|
|
|
689
|
+
// \u9031\u5225hover\u3060\u3051\u306F\u3001\u305D\u306E\u9031\u306E\u5185\u8A33\u3092\u91D1\u984D\u306E\u5927\u304D\u3044\u9806\u306B\u8AAD\u3080\u3002\u7A4D\u307F\u4E0A\u3052\u9806\u30FB\u8272\u306F
|
|
690
|
+
// slotOrder\u306E\u307E\u307E\u56FA\u5B9A\u3057\u3001\u540C\u984D\u3082slotOrder\u3067\u6C7A\u5B9A\u7684\u306B\u4E26\u3079\u308B\u3002
|
|
691
|
+
function tooltipSlotOrder(bucket, gran){
|
|
692
|
+
var order = slotOrder.slice();
|
|
693
|
+
if(gran !== 'week') return order;
|
|
694
|
+
var rank = {}; for(var i=0;i<slotOrder.length;i++){ rank[slotOrder[i]] = i; }
|
|
695
|
+
order.sort(function(a, b){
|
|
696
|
+
var diff = (bucket.bs[b] || 0) - (bucket.bs[a] || 0);
|
|
697
|
+
return diff !== 0 ? diff : rank[a] - rank[b];
|
|
698
|
+
});
|
|
699
|
+
return order;
|
|
700
|
+
}
|
|
701
|
+
|
|
684
702
|
function showTip(evt, bucket){
|
|
685
703
|
if(!tip) return;
|
|
686
704
|
clearNode(tip);
|
|
687
705
|
var h = document.createElement('div'); h.className = 'tip-title';
|
|
688
706
|
h.textContent = periodText(bucket.key, GRAN) + ' \u5408\u8A08 ' + formatUSD(bucket.total) + ' \xB7 ' + bucket.turns + ' \u30BF\u30FC\u30F3';
|
|
689
707
|
tip.appendChild(h);
|
|
690
|
-
|
|
691
|
-
|
|
708
|
+
var tipOrder = tooltipSlotOrder(bucket, GRAN);
|
|
709
|
+
for(var j=0;j<tipOrder.length;j++){
|
|
710
|
+
var slot = tipOrder[j];
|
|
692
711
|
var v = bucket.bs[slot] || 0;
|
|
693
712
|
if(v <= 0) continue;
|
|
694
713
|
var row = document.createElement('div'); row.className = 'tip-row';
|
|
@@ -825,12 +844,13 @@ var APP_JS = `<script>
|
|
|
825
844
|
sub.textContent = ' ' + mk + (mk === curMonth ? '(\u4ECA\u6708)' : '');
|
|
826
845
|
h.appendChild(sub);
|
|
827
846
|
budgetEl.appendChild(h);
|
|
828
|
-
// \u671F\u9593\u9650\u5B9A\u7248\u306F\
|
|
847
|
+
// \u671F\u9593\u9650\u5B9A\u7248\u306F\u3001\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u306E\u5F53\u6708\u5206\u3092\u96C6\u8A08\u5BFE\u8C61\u304B\u3089\u843D\u3068\u3055\u306A\u3044\u305F\u3081
|
|
848
|
+
// \u57CB\u3081\u8FBC\u307E\u306A\u3044\u5168\u5C65\u6B74\u304B\u3089\u56FA\u5B9A\u5024\u3092\u4F5C\u308B\u3002
|
|
829
849
|
// \u5168\u5C65\u6B74\u7248\u306E\u307F\u5F93\u6765\u3069\u304A\u308A\u9078\u629E\u6708\u3078\u9023\u52D5\u3059\u308B\u3002\u3044\u305A\u308C\u3082\u30BD\u30FC\u30B9\u30D5\u30A3\u30EB\u30BF\u975E\u9023\u52D5\u3002
|
|
830
850
|
if(BUDGET_FIXED || HAS_CODEX){
|
|
831
851
|
var srcNote = document.createElement('p'); srcNote.className = 'note';
|
|
832
852
|
srcNote.textContent = BUDGET_FIXED
|
|
833
|
-
? '\u4ECA\u6708\u30FB\
|
|
853
|
+
? '\u4ECA\u6708\u30FB\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u3092\u5168\u4EF6\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / current month from all recorded history'
|
|
834
854
|
: '\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources';
|
|
835
855
|
budgetEl.appendChild(srcNote);
|
|
836
856
|
}
|
|
@@ -1062,7 +1082,7 @@ function budgetCard(budgetUSD, month, fallbackRate, hasCodex, fixedCurrentMonth)
|
|
|
1062
1082
|
const width = Math.max(0, Math.min(100, pct));
|
|
1063
1083
|
const level = pct >= 100 ? "over" : pct >= 70 ? "warn" : "ok";
|
|
1064
1084
|
const budgetJpy = budgetUSD * fallbackRate;
|
|
1065
|
-
return `<section class="card" id="cccn-budget"><h2>\u6708\u4E88\u7B97 / Monthly budget<span class="stat-sub"> \u4ECA\u6708(\u66A6\u6708)</span></h2>` + (fixedCurrentMonth ? `<p class="note">\u4ECA\u6708\u30FB\
|
|
1085
|
+
return `<section class="card" id="cccn-budget"><h2>\u6708\u4E88\u7B97 / Monthly budget<span class="stat-sub"> \u4ECA\u6708(\u66A6\u6708)</span></h2>` + (fixedCurrentMonth ? `<p class="note">\u4ECA\u6708\u30FB\u4FDD\u5B58\u6E08\u307F\u5C65\u6B74\u3092\u5168\u4EF6\u96C6\u8A08(\u5168\u30BD\u30FC\u30B9\u5408\u7B97) / current month from all recorded history</p>` : hasCodex ? `<p class="note">\u5168\u30BD\u30FC\u30B9\u5408\u7B97 / all sources</p>` : "") + `<div class="budget-bar"><div class="budget-fill lvl-${level}" style="width:${width.toFixed(1)}%"></div></div><div class="budget-foot"><span>\u4ECA\u6708 <b>${esc(formatUSD(month.usd))}</b> / ${esc(formatUSD(budgetUSD))} <span class="muted">(${esc(formatJPY(month.jpy))} / ${esc(formatJPY(budgetJpy))})</span></span><span class="budget-pct lvl-${level}">${pct.toFixed(1)}% used</span></div></section>`;
|
|
1066
1086
|
}
|
|
1067
1087
|
function escapeJsonForScript(json) {
|
|
1068
1088
|
return json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
@@ -1071,7 +1091,7 @@ function renderDashboard(turns, opts, fullHistory) {
|
|
|
1071
1091
|
const version = readVersion();
|
|
1072
1092
|
const generatedAtIso = opts.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1073
1093
|
const generatedAt = fmtLocalDateTime(generatedAtIso);
|
|
1074
|
-
|
|
1094
|
+
let period = opts.days === null ? "\u5168\u671F\u9593" : `\u76F4\u8FD1 ${opts.days} \u65E5\u9593`;
|
|
1075
1095
|
const limited = opts.days !== null;
|
|
1076
1096
|
const variant = opts.variant ?? "custom";
|
|
1077
1097
|
const totalLabel = limited ? "\u5BFE\u8C61\u671F\u9593\u5408\u8A08" : "\u901A\u7B97";
|
|
@@ -1096,13 +1116,17 @@ function renderDashboard(turns, opts, fullHistory) {
|
|
|
1096
1116
|
}
|
|
1097
1117
|
let minMs = Infinity;
|
|
1098
1118
|
let maxMs = -Infinity;
|
|
1099
|
-
for (const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1119
|
+
for (const turn of turns) {
|
|
1120
|
+
const ms = Date.parse(turn.ts);
|
|
1121
|
+
if (!Number.isFinite(ms)) continue;
|
|
1122
|
+
if (ms < minMs) minMs = ms;
|
|
1123
|
+
if (ms > maxMs) maxMs = ms;
|
|
1104
1124
|
}
|
|
1105
1125
|
const rangeText = Number.isFinite(minMs) && Number.isFinite(maxMs) ? `${dateKeyOf(new Date(minMs))} \u301C ${dateKeyOf(new Date(maxMs))}` : "\u2014";
|
|
1126
|
+
const embeddedDaySpan = Number.isFinite(minMs) && Number.isFinite(maxMs) ? inclusiveLocalDateSpan(new Date(minMs), new Date(maxMs)) : null;
|
|
1127
|
+
const shortRecentHistory = opts.days !== null && embeddedDaySpan !== null && embeddedDaySpan < opts.days;
|
|
1128
|
+
if (shortRecentHistory) period = `\u5C65\u6B74 ${embeddedDaySpan} \u65E5\u5206`;
|
|
1129
|
+
const recentVariantLabel = shortRecentHistory ? `\u5C65\u6B74 ${embeddedDaySpan} \u65E5\u5206 / Recent` : `\u76F4\u8FD1 ${opts.days} \u65E5\u7248 / Recent`;
|
|
1106
1130
|
const embed = {
|
|
1107
1131
|
version,
|
|
1108
1132
|
generatedAt,
|
|
@@ -1119,8 +1143,8 @@ function renderDashboard(turns, opts, fullHistory) {
|
|
|
1119
1143
|
const reloadSec = Number.isFinite(opts.autoReloadSec) && opts.autoReloadSec > 0 ? Math.floor(opts.autoReloadSec) : 0;
|
|
1120
1144
|
const refreshMeta = reloadSec > 0 ? `<meta http-equiv="refresh" content="${reloadSec}">` : "";
|
|
1121
1145
|
const updateTrigger = anyCodex ? "Claude Code / Codex \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642" : "Claude Code \u306E\u5FDC\u7B54\u5B8C\u4E86\u6642";
|
|
1122
|
-
const autoUpdateFoot = reloadSec > 0 ? variant === "full" ? `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u30D5\u30A1\u30A4\u30EB\u3092\u518D\u8AAD\u8FBC(\u5168\u5C65\u6B74\u7248\u306E\u751F\u6210\u306F\u30ED\u30FC\u30AB\u30EB\u65E5\u3054\u3068\u3001\u307E\u305F\u306F\u624B\u52D5 dashboard --all)</div>` : `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u81EA\u52D5\u66F4\u65B0(\u6700\u65B0\u5316\u306F ${updateTrigger})</div>` : "";
|
|
1123
|
-
const variantNav = variant === "recent" ? `<div class="sub"><strong
|
|
1146
|
+
const autoUpdateFoot = reloadSec > 0 ? variant === "full" ? `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u30D5\u30A1\u30A4\u30EB\u3092\u518D\u8AAD\u8FBC(\u5168\u5C65\u6B74\u7248\u306E\u751F\u6210\u306F\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642\u3001\u30ED\u30FC\u30AB\u30EB\u65E5\u3054\u3068\u3001\u307E\u305F\u306F\u624B\u52D5 dashboard --all)</div>` : `<div class="foot">\u7D04 ${reloadSec} \u79D2\u3054\u3068\u306B\u81EA\u52D5\u66F4\u65B0(\u6700\u65B0\u5316\u306F ${updateTrigger}\u3001\u307E\u305F\u306F\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642)</div>` : "";
|
|
1147
|
+
const variantNav = variant === "recent" ? `<div class="sub"><strong>${recentVariantLabel}</strong> \xB7 ${opts.peerAvailable ? `<a href="report-all.html">\u5168\u5C65\u6B74\u7248\u3078 / Full history</a>` : `<span aria-disabled="true">\u5168\u5C65\u6B74\u7248\u306F\u672A\u751F\u6210\u3067\u3059\uFF08dashboard --all \u3067\u751F\u6210\uFF09 / Full history not generated</span>`}</div>` : variant === "full" ? `<div class="sub"><strong>\u5168\u5C65\u6B74\u7248 / Full history</strong> \xB7 ${opts.peerAvailable ? `<a href="report.html">\u76F4\u8FD1\u7248\u3078 / Recent</a>` : `<span aria-disabled="true">\u76F4\u8FD1\u7248\u306F\u672A\u751F\u6210\u3067\u3059\uFF08dashboard \u3067\u751F\u6210\uFF09 / Recent not generated</span>`}</div><div class="sub muted">\u6700\u7D42\u751F\u6210 ${esc(generatedAt)}\u3002\u6B63\u5E38\u306A sweep \u5B8C\u4E86\u6642\u3001\u30ED\u30FC\u30AB\u30EB\u65E5\u306E\u6700\u521D\u306E\u6B63\u5E38\u306A\u30BF\u30FC\u30F3\u6642\u3001\u307E\u305F\u306F\u624B\u52D5\u306E dashboard --all \u3067\u66F4\u65B0\u3055\u308C\u307E\u3059\u3002</div>` : "";
|
|
1124
1148
|
const head = `<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light dark">` + refreshMeta + `<title>ccc-notifier \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9</title>` + STYLE + `</head><body><div class="wrap">`;
|
|
1125
1149
|
const foot = autoUpdateFoot + `<div class="foot">ccc-notifier v${esc(version)} \xB7 \u30C7\u30FC\u30BF\u306F\u30ED\u30FC\u30AB\u30EB\u306E\u307F / all data stays local</div></div><div id="cccn-tip" class="cccn-tip" hidden></div><script id="cccn-data" type="application/json">${dataJson}</script>` + APP_JS + `</body></html>`;
|
|
1126
1150
|
const budgetSection = budgetCard(budgetUSD, budgetMonth, cfg.fx.fallbackRate, anyCodex, limited);
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/pricing.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
7
|
+
var LITELLM_FETCH_TIMEOUT_MS = 3e3;
|
|
8
|
+
var CACHE_FRESH_MS = 24 * 60 * 60 * 1e3;
|
|
9
|
+
var SONNET_5_STANDARD_PRICE_START_MS = Date.UTC(2026, 8, 1);
|
|
10
|
+
function price(input, output, cacheWrite5m, cacheWrite1h, cacheRead, source) {
|
|
11
|
+
return { input, output, cacheWrite5m, cacheWrite1h, cacheRead, source };
|
|
12
|
+
}
|
|
13
|
+
function builtinPriceTable(now = /* @__PURE__ */ new Date()) {
|
|
14
|
+
const sonnet5 = now.getTime() < SONNET_5_STANDARD_PRICE_START_MS ? price(2, 10, 2.5, 4, 0.2, "builtin") : price(3, 15, 3.75, 6, 0.3, "builtin");
|
|
15
|
+
return {
|
|
16
|
+
"claude-fable-5": price(10, 50, 12.5, 20, 1, "builtin"),
|
|
17
|
+
"claude-mythos-5": price(10, 50, 12.5, 20, 1, "builtin"),
|
|
18
|
+
"claude-opus-4-8": price(5, 25, 6.25, 10, 0.5, "builtin"),
|
|
19
|
+
"claude-opus-4-7": price(5, 25, 6.25, 10, 0.5, "builtin"),
|
|
20
|
+
"claude-opus-4-6": price(5, 25, 6.25, 10, 0.5, "builtin"),
|
|
21
|
+
"claude-opus-4-5": price(5, 25, 6.25, 10, 0.5, "builtin"),
|
|
22
|
+
"claude-opus-4-1": price(15, 75, 18.75, 30, 1.5, "builtin"),
|
|
23
|
+
"claude-opus-4": price(15, 75, 18.75, 30, 1.5, "builtin"),
|
|
24
|
+
// 旧 claude-opus-4-20250514 の受け皿
|
|
25
|
+
"claude-3-opus": price(15, 75, 18.75, 30, 1.5, "builtin"),
|
|
26
|
+
"claude-sonnet-5": sonnet5,
|
|
27
|
+
"claude-sonnet-4-6": price(3, 15, 3.75, 6, 0.3, "builtin"),
|
|
28
|
+
"claude-sonnet-4-5": price(3, 15, 3.75, 6, 0.3, "builtin"),
|
|
29
|
+
"claude-sonnet-4": price(3, 15, 3.75, 6, 0.3, "builtin"),
|
|
30
|
+
"claude-3-7-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
|
|
31
|
+
"claude-3-5-sonnet": price(3, 15, 3.75, 6, 0.3, "builtin"),
|
|
32
|
+
"claude-haiku-4-5": price(1, 5, 1.25, 2, 0.1, "builtin"),
|
|
33
|
+
"claude-3-5-haiku": price(0.8, 4, 1, 1.6, 0.08, "builtin"),
|
|
34
|
+
"claude-3-haiku": price(0.25, 1.25, 0.3125, 0.5, 0.025, "builtin"),
|
|
35
|
+
// OpenAI Codex CLI 対応(公式レートに基づく単価。キャッシュ書き込み課金は無いため 0)
|
|
36
|
+
"gpt-5.5": price(5, 30, 0, 0, 0.5, "builtin"),
|
|
37
|
+
"gpt-5.1": price(1.25, 10, 0, 0, 0.125, "builtin"),
|
|
38
|
+
"gpt-5": price(1.25, 10, 0, 0, 0.125, "builtin"),
|
|
39
|
+
"gpt-5-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
|
|
40
|
+
"gpt-5.1-codex": price(1.25, 10, 0, 0, 0.125, "builtin"),
|
|
41
|
+
"o3": price(2, 8, 0, 0, 0.5, "builtin")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function normalizeModelId(modelId) {
|
|
45
|
+
let s = modelId.toLowerCase();
|
|
46
|
+
s = s.replace(/^anthropic[\/.]/, "");
|
|
47
|
+
s = s.replace(/\[1m\]$/, "");
|
|
48
|
+
s = s.replace(/-20\d{6}$/, "");
|
|
49
|
+
return s.trim();
|
|
50
|
+
}
|
|
51
|
+
function resolvePrice(modelId, table) {
|
|
52
|
+
const target = normalizeModelId(modelId);
|
|
53
|
+
for (const rawKey of Object.keys(table)) {
|
|
54
|
+
const key = normalizeModelId(rawKey);
|
|
55
|
+
if (key.length > 0 && target === key) return { ...table[rawKey] };
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function computeCost(main, sidechain, table) {
|
|
60
|
+
const byModel = /* @__PURE__ */ Object.create(null);
|
|
61
|
+
const unknownModels = [];
|
|
62
|
+
let usd = 0;
|
|
63
|
+
const accumulate = (usage) => {
|
|
64
|
+
for (const [model, tokens] of Object.entries(usage)) {
|
|
65
|
+
const p = resolvePrice(model, table);
|
|
66
|
+
let cost = 0;
|
|
67
|
+
if (p === null) {
|
|
68
|
+
if (!unknownModels.includes(model)) unknownModels.push(model);
|
|
69
|
+
} else {
|
|
70
|
+
cost = (tokens.input * p.input + tokens.output * p.output + tokens.cacheWrite5m * p.cacheWrite5m + tokens.cacheWrite1h * p.cacheWrite1h + tokens.cacheRead * p.cacheRead) / 1e6;
|
|
71
|
+
}
|
|
72
|
+
byModel[model] = (Object.hasOwn(byModel, model) ? byModel[model] : 0) + cost;
|
|
73
|
+
usd += cost;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
accumulate(main);
|
|
77
|
+
accumulate(sidechain);
|
|
78
|
+
return { usd, byModel, unknownModels };
|
|
79
|
+
}
|
|
80
|
+
function cacheFilePath(cacheDir) {
|
|
81
|
+
return path.join(cacheDir, "pricing.json");
|
|
82
|
+
}
|
|
83
|
+
async function readPriceCache(cacheDir) {
|
|
84
|
+
try {
|
|
85
|
+
const raw = await fs.readFile(cacheFilePath(cacheDir), "utf8");
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.fetchedAt === "string" && typeof parsed.table === "object" && parsed.table !== null) {
|
|
88
|
+
const p = parsed;
|
|
89
|
+
return { fetchedAt: p.fetchedAt, table: p.table };
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function writePriceCache(cacheDir, table) {
|
|
97
|
+
const file = cacheFilePath(cacheDir);
|
|
98
|
+
const payload = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), table };
|
|
99
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
100
|
+
await fs.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
|
|
101
|
+
}
|
|
102
|
+
function isCacheFresh(fetchedAt) {
|
|
103
|
+
const t = Date.parse(fetchedAt);
|
|
104
|
+
if (Number.isNaN(t)) return false;
|
|
105
|
+
return Date.now() - t <= CACHE_FRESH_MS;
|
|
106
|
+
}
|
|
107
|
+
function mergePriceTables(builtin, cached, fresh) {
|
|
108
|
+
const merged = { ...builtin };
|
|
109
|
+
const builtinKeyById = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const rawKey of Object.keys(builtin)) {
|
|
111
|
+
const normalized = normalizeModelId(rawKey);
|
|
112
|
+
if (normalized.length > 0) builtinKeyById.set(normalized, rawKey);
|
|
113
|
+
}
|
|
114
|
+
const cachedKeyById = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const [model, modelPrice] of Object.entries(cached)) {
|
|
116
|
+
const normalized = normalizeModelId(model);
|
|
117
|
+
if (normalized.length === 0) continue;
|
|
118
|
+
if (normalized === "claude-sonnet-5") continue;
|
|
119
|
+
const builtinKey = builtinKeyById.get(normalized);
|
|
120
|
+
if (builtinKey !== void 0) {
|
|
121
|
+
if (fresh) merged[builtinKey] = modelPrice;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const priorCachedKey = cachedKeyById.get(normalized);
|
|
125
|
+
if (priorCachedKey !== void 0 && priorCachedKey !== model) delete merged[priorCachedKey];
|
|
126
|
+
Object.defineProperty(merged, model, {
|
|
127
|
+
value: modelPrice,
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true
|
|
131
|
+
});
|
|
132
|
+
cachedKeyById.set(normalized, model);
|
|
133
|
+
}
|
|
134
|
+
return merged;
|
|
135
|
+
}
|
|
136
|
+
function toFiniteNumber(v) {
|
|
137
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
138
|
+
}
|
|
139
|
+
var LITELLM_OPENAI_KEY_RE = /^(gpt-|o3($|-)|codex-)/;
|
|
140
|
+
function convertLiteLLMPayload(payload) {
|
|
141
|
+
if (payload === null || typeof payload !== "object") {
|
|
142
|
+
throw new Error("invalid litellm payload: not an object");
|
|
143
|
+
}
|
|
144
|
+
const table = {};
|
|
145
|
+
for (const [rawKey, rawEntry] of Object.entries(payload)) {
|
|
146
|
+
if (rawEntry === null || typeof rawEntry !== "object") continue;
|
|
147
|
+
const entry = rawEntry;
|
|
148
|
+
const provider = entry.litellm_provider;
|
|
149
|
+
if (provider === "openai") {
|
|
150
|
+
const key2 = rawKey.toLowerCase();
|
|
151
|
+
if (!LITELLM_OPENAI_KEY_RE.test(key2)) continue;
|
|
152
|
+
const inputRaw2 = toFiniteNumber(entry.input_cost_per_token);
|
|
153
|
+
const outputRaw2 = toFiniteNumber(entry.output_cost_per_token);
|
|
154
|
+
if (inputRaw2 === null || inputRaw2 <= 0) continue;
|
|
155
|
+
if (outputRaw2 === null || outputRaw2 <= 0) continue;
|
|
156
|
+
const cacheReadRaw2 = toFiniteNumber(entry.cache_read_input_token_cost);
|
|
157
|
+
table[key2] = {
|
|
158
|
+
input: inputRaw2 * 1e6,
|
|
159
|
+
output: outputRaw2 * 1e6,
|
|
160
|
+
cacheRead: cacheReadRaw2 !== null ? cacheReadRaw2 * 1e6 : 0,
|
|
161
|
+
cacheWrite5m: 0,
|
|
162
|
+
cacheWrite1h: 0,
|
|
163
|
+
source: "litellm"
|
|
164
|
+
};
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (typeof provider === "string" && provider !== "anthropic") continue;
|
|
168
|
+
let key = rawKey.toLowerCase();
|
|
169
|
+
if (key.startsWith("anthropic/")) key = key.slice("anthropic/".length);
|
|
170
|
+
if (!key.startsWith("claude")) continue;
|
|
171
|
+
const inputRaw = toFiniteNumber(entry.input_cost_per_token);
|
|
172
|
+
const outputRaw = toFiniteNumber(entry.output_cost_per_token);
|
|
173
|
+
if (inputRaw === null || inputRaw <= 0) continue;
|
|
174
|
+
if (outputRaw === null || outputRaw <= 0) continue;
|
|
175
|
+
const input = inputRaw * 1e6;
|
|
176
|
+
const output = outputRaw * 1e6;
|
|
177
|
+
const cacheReadRaw = toFiniteNumber(entry.cache_read_input_token_cost);
|
|
178
|
+
const cacheWrite5mRaw = toFiniteNumber(entry.cache_creation_input_token_cost);
|
|
179
|
+
const cacheWrite1hRaw = toFiniteNumber(entry.cache_creation_input_token_cost_above_1hr);
|
|
180
|
+
table[key] = {
|
|
181
|
+
input,
|
|
182
|
+
output,
|
|
183
|
+
cacheRead: cacheReadRaw !== null ? cacheReadRaw * 1e6 : input * 0.1,
|
|
184
|
+
cacheWrite5m: cacheWrite5mRaw !== null ? cacheWrite5mRaw * 1e6 : input * 1.25,
|
|
185
|
+
cacheWrite1h: cacheWrite1hRaw !== null ? cacheWrite1hRaw * 1e6 : input * 2,
|
|
186
|
+
source: "litellm"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return table;
|
|
190
|
+
}
|
|
191
|
+
async function fetchLiteLLMPriceTable() {
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const timer = setTimeout(() => controller.abort(), LITELLM_FETCH_TIMEOUT_MS);
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetch(LITELLM_URL, { signal: controller.signal });
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
throw new Error(`litellm fetch failed with status ${res.status}`);
|
|
198
|
+
}
|
|
199
|
+
const json = await res.json();
|
|
200
|
+
return convertLiteLLMPayload(json);
|
|
201
|
+
} finally {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function loadPriceTable(cacheDir, opts) {
|
|
206
|
+
const builtin = builtinPriceTable();
|
|
207
|
+
const cached = await readPriceCache(cacheDir);
|
|
208
|
+
if (cached !== null && isCacheFresh(cached.fetchedAt)) {
|
|
209
|
+
return mergePriceTables(builtin, cached.table, true);
|
|
210
|
+
}
|
|
211
|
+
if (opts?.offline === true) {
|
|
212
|
+
return cached !== null ? mergePriceTables(builtin, cached.table, false) : builtin;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const remoteTable = await fetchLiteLLMPriceTable();
|
|
216
|
+
await writePriceCache(cacheDir, remoteTable);
|
|
217
|
+
return mergePriceTables(builtin, remoteTable, true);
|
|
218
|
+
} catch {
|
|
219
|
+
return cached !== null ? mergePriceTables(builtin, cached.table, false) : builtin;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
computeCost,
|
|
225
|
+
loadPriceTable
|
|
226
|
+
};
|
|
@@ -7,11 +7,14 @@ import {
|
|
|
7
7
|
codexHome,
|
|
8
8
|
detectCodex
|
|
9
9
|
} from "./chunk-HTYUYKFW.js";
|
|
10
|
+
import {
|
|
11
|
+
loadPriceTable
|
|
12
|
+
} from "./chunk-6HTETN26.js";
|
|
10
13
|
import {
|
|
11
14
|
configFilePath,
|
|
12
15
|
paths,
|
|
13
16
|
readConfig
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-OOAC5ULQ.js";
|
|
15
18
|
|
|
16
19
|
// src/setup.ts
|
|
17
20
|
import {
|
|
@@ -303,8 +306,8 @@ function makeTestRecord() {
|
|
|
303
306
|
sidechainTokens: null,
|
|
304
307
|
apiCalls: 1,
|
|
305
308
|
costUSD: 0.01,
|
|
306
|
-
costJPY: 1.
|
|
307
|
-
fxRate:
|
|
309
|
+
costJPY: 1.6,
|
|
310
|
+
fxRate: 160,
|
|
308
311
|
fxSource: "fixed",
|
|
309
312
|
prompt: "\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86\u30C6\u30B9\u30C8"
|
|
310
313
|
};
|
|
@@ -635,6 +638,7 @@ async function runInit(argv) {
|
|
|
635
638
|
if (installCodex) {
|
|
636
639
|
codexResult = registerCodexHook(process.execPath, resolveCliPath());
|
|
637
640
|
}
|
|
641
|
+
await loadPriceTable(cccn.cacheDir, { offline: false });
|
|
638
642
|
const notifyDisabled = !cfg.notify.os && !cfg.notify.slack;
|
|
639
643
|
if (notifyDisabled) {
|
|
640
644
|
console.log("\u30C6\u30B9\u30C8\u901A\u77E5: \u901A\u77E5\u306A\u3057\u30E2\u30FC\u30C9\u306E\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");
|