sidekick-shared 0.28.2 → 0.29.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/CHANGELOG.md +83 -0
- package/README.md +176 -30
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +14 -6
- package/dist/browser.js +26 -1
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/codexQuota.js +17 -4
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +6 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/codexQuota.js +18 -5
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +12 -9
- package/dist/esm/modelCatalog.js +2 -1
- package/dist/esm/modelInfo.js +422 -45
- package/dist/esm/multiProviderQuotaService.js +1 -1
- package/dist/esm/node.js +9 -3
- package/dist/esm/parsers/codexParser.js +175 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +83 -21
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +14 -7
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/codex.js +2 -24
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/quota.js +356 -24
- package/dist/esm/quotaHistory.js +33 -8
- package/dist/esm/quotaPoller.js +5 -2
- package/dist/esm/quotaResolver.js +31 -6
- package/dist/esm/quotaSnapshots.js +65 -10
- package/dist/esm/quotaThresholds.js +7 -2
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/index.js +2 -2
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/quota.js +30 -0
- package/dist/esm/schemas/quotaHistory.js +10 -2
- package/dist/esm/schemas/stateFile.js +9 -0
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/stateFile.js +18 -3
- package/dist/esm/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/esm/statusline/formatter.js +57 -18
- package/dist/esm/statusline/index.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +42 -3
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/jsonlTail.js +378 -59
- package/dist/esm/zaiQuotaApi.js +4 -2
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -16
- package/dist/index.js +48 -12
- package/dist/modelCatalog.js +2 -1
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +431 -47
- package/dist/multiProviderQuotaService.js +1 -1
- package/dist/node.d.ts +16 -5
- package/dist/node.js +31 -2
- package/dist/parsers/codexParser.d.ts +55 -0
- package/dist/parsers/codexParser.js +175 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +86 -21
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +13 -6
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +3 -26
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/quota.d.ts +131 -1
- package/dist/quota.js +366 -24
- package/dist/quotaHistory.d.ts +10 -0
- package/dist/quotaHistory.js +34 -8
- package/dist/quotaPoller.d.ts +11 -3
- package/dist/quotaPoller.js +5 -2
- package/dist/quotaResolver.js +30 -5
- package/dist/quotaSnapshots.js +64 -9
- package/dist/quotaThresholds.js +7 -2
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +3 -3
- package/dist/schemas/index.js +5 -2
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/quota.d.ts +258 -0
- package/dist/schemas/quota.js +31 -1
- package/dist/schemas/quotaHistory.d.ts +2 -0
- package/dist/schemas/quotaHistory.js +10 -2
- package/dist/schemas/stateFile.d.ts +34 -0
- package/dist/schemas/stateFile.js +10 -1
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/stateFile.d.ts +18 -0
- package/dist/stateFile.js +17 -2
- package/dist/statusline/claudeStatuslinePayload.d.ts +4 -3
- package/dist/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/statusline/formatter.d.ts +5 -0
- package/dist/statusline/formatter.js +56 -17
- package/dist/statusline/index.d.ts +4 -4
- package/dist/statusline/index.js +5 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +42 -3
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/jsonlTail.d.ts +101 -4
- package/dist/watchers/jsonlTail.js +380 -60
- package/dist/zaiQuotaApi.js +3 -1
- package/package.json +41 -1
package/dist/modelInfo.js
CHANGED
|
@@ -15,17 +15,26 @@
|
|
|
15
15
|
* baseline without requiring a redeploy when vendor prices change.
|
|
16
16
|
* - Longest-prefix matching handles variants like `claude-opus-4.5-20250514`
|
|
17
17
|
* against a key like `claude-opus-4.5`. Same pattern used in modelContext.ts.
|
|
18
|
+
* - A consumer layer (`registerModelPricing`) sits beside the catalog and
|
|
19
|
+
* imported layers and survives their resets.
|
|
20
|
+
* - Every table-derived value is computed lazily, so a tree-shaken bundle that
|
|
21
|
+
* only uses the parsing/display helpers does not retain the pricing table.
|
|
18
22
|
*
|
|
19
23
|
* @module modelInfo
|
|
20
24
|
*/
|
|
21
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.MODEL_PROVIDER_IDS = void 0;
|
|
26
|
+
exports.formatCost = exports.MODEL_PROVIDER_IDS = void 0;
|
|
23
27
|
exports._setPricingOverrides = _setPricingOverrides;
|
|
24
28
|
exports._getPricingOverrides = _getPricingOverrides;
|
|
25
29
|
exports._clearPricingOverrides = _clearPricingOverrides;
|
|
26
30
|
exports._setImportedResolvedPricing = _setImportedResolvedPricing;
|
|
27
31
|
exports._clearImportedResolvedPricing = _clearImportedResolvedPricing;
|
|
32
|
+
exports.registerModelPricing = registerModelPricing;
|
|
33
|
+
exports.clearRegisteredModelPricing = clearRegisteredModelPricing;
|
|
34
|
+
exports.getPricingCatalogInfo = getPricingCatalogInfo;
|
|
28
35
|
exports._getModelPricingIds = _getModelPricingIds;
|
|
36
|
+
exports.canonicalModelId = canonicalModelId;
|
|
37
|
+
exports.isMovingModelAlias = isMovingModelAlias;
|
|
29
38
|
exports.parseModelId = parseModelId;
|
|
30
39
|
exports.resolveModelPricing = resolveModelPricing;
|
|
31
40
|
exports.getModelPricing = getModelPricing;
|
|
@@ -34,15 +43,18 @@ exports.calculateCostWithPricing = calculateCostWithPricing;
|
|
|
34
43
|
exports.calculateCost = calculateCost;
|
|
35
44
|
exports.calculateCostWithProvenance = calculateCostWithProvenance;
|
|
36
45
|
exports.mergeCostSources = mergeCostSources;
|
|
46
|
+
exports.costSourceFromPricingProvenance = costSourceFromPricingProvenance;
|
|
47
|
+
exports.mergeCostComponents = mergeCostComponents;
|
|
37
48
|
exports.shortModelName = shortModelName;
|
|
38
49
|
exports.getModelDisplayInfo = getModelDisplayInfo;
|
|
39
50
|
exports.compareModelIds = compareModelIds;
|
|
40
51
|
exports.sortModelIds = sortModelIds;
|
|
41
|
-
exports.formatCost = formatCost;
|
|
42
52
|
const modelContext_1 = require("./modelContext");
|
|
43
53
|
const modelAliases_1 = require("./modelAliases");
|
|
44
54
|
var providerIds_1 = require("./providerIds");
|
|
45
55
|
Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.MODEL_PROVIDER_IDS; } });
|
|
56
|
+
var formatting_1 = require("./formatting");
|
|
57
|
+
Object.defineProperty(exports, "formatCost", { enumerable: true, get: function () { return formatting_1.formatCost; } });
|
|
46
58
|
// ── Static Pricing Table ──
|
|
47
59
|
/**
|
|
48
60
|
* Static baseline pricing. Keys are model-ID prefixes; lookup uses
|
|
@@ -644,21 +656,38 @@ const PRICING_TABLE = {
|
|
|
644
656
|
cacheReadCostPerMillion: 7.5,
|
|
645
657
|
},
|
|
646
658
|
};
|
|
647
|
-
/**
|
|
648
|
-
|
|
659
|
+
/**
|
|
660
|
+
* Static-table keys sorted longest-first so longest-prefix wins. Computed on
|
|
661
|
+
* first lookup rather than at module load: a top-level call would keep the
|
|
662
|
+
* whole table alive in tree-shaken bundles that never price anything.
|
|
663
|
+
*/
|
|
664
|
+
let staticSortedKeys = null;
|
|
665
|
+
function getStaticSortedKeys() {
|
|
666
|
+
if (!staticSortedKeys) {
|
|
667
|
+
staticSortedKeys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
|
|
668
|
+
}
|
|
669
|
+
return staticSortedKeys;
|
|
670
|
+
}
|
|
649
671
|
// ── Override Table (populated at runtime by pricingCatalog) ──
|
|
650
672
|
let overrideTable = {};
|
|
651
673
|
let overrideSortedKeys = [];
|
|
674
|
+
let overrideBasis = null;
|
|
652
675
|
let importedTable = {};
|
|
653
676
|
/**
|
|
654
677
|
* Internal: replace the runtime override map. Called by `pricingCatalog.ts`
|
|
655
678
|
* after a successful LiteLLM hydration. Not part of the stable public API,
|
|
656
679
|
* but exported so the Node-only catalog module can wire itself in without
|
|
657
680
|
* creating a circular import.
|
|
681
|
+
*
|
|
682
|
+
* `basis` records how the map was obtained for `getPricingCatalogInfo()`;
|
|
683
|
+
* omitted, a non-empty map reports as `litellm` with no `fetchedAt`. Every
|
|
684
|
+
* call replaces the previous basis.
|
|
658
685
|
*/
|
|
659
|
-
function _setPricingOverrides(overrides) {
|
|
686
|
+
function _setPricingOverrides(overrides, basis) {
|
|
660
687
|
overrideTable = Object.fromEntries(Object.entries(overrides).map(([key, value]) => [key.trim().toLowerCase(), { ...value }]));
|
|
661
688
|
overrideSortedKeys = Object.keys(overrideTable).sort((a, b) => b.length - a.length);
|
|
689
|
+
overrideBasis = basis ? { ...basis } : null;
|
|
690
|
+
invalidatePricingDigest();
|
|
662
691
|
}
|
|
663
692
|
/** Internal: snapshot current overrides (test + diagnostics). */
|
|
664
693
|
function _getPricingOverrides() {
|
|
@@ -668,6 +697,8 @@ function _getPricingOverrides() {
|
|
|
668
697
|
function _clearPricingOverrides() {
|
|
669
698
|
overrideTable = {};
|
|
670
699
|
overrideSortedKeys = [];
|
|
700
|
+
overrideBasis = null;
|
|
701
|
+
invalidatePricingDigest();
|
|
671
702
|
}
|
|
672
703
|
/**
|
|
673
704
|
* Internal: hydrate exact pricing resolutions exported by another realm.
|
|
@@ -676,10 +707,210 @@ function _clearPricingOverrides() {
|
|
|
676
707
|
*/
|
|
677
708
|
function _setImportedResolvedPricing(values) {
|
|
678
709
|
importedTable = { ...importedTable, ...values };
|
|
710
|
+
invalidatePricingDigest();
|
|
679
711
|
}
|
|
680
712
|
/** Internal test hook. */
|
|
681
713
|
function _clearImportedResolvedPricing() {
|
|
682
714
|
importedTable = {};
|
|
715
|
+
invalidatePricingDigest();
|
|
716
|
+
}
|
|
717
|
+
// A Map (created on first registration) so keys such as `__proto__` stay data.
|
|
718
|
+
let registeredTable = null;
|
|
719
|
+
function isValidModelPricing(value) {
|
|
720
|
+
if (!value || typeof value !== 'object')
|
|
721
|
+
return false;
|
|
722
|
+
const pricing = value;
|
|
723
|
+
return [
|
|
724
|
+
pricing.inputCostPerMillion,
|
|
725
|
+
pricing.outputCostPerMillion,
|
|
726
|
+
pricing.cacheWriteCostPerMillion,
|
|
727
|
+
pricing.cacheReadCostPerMillion,
|
|
728
|
+
].every((item) => typeof item === 'number' && Number.isFinite(item) && item >= 0);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Register consumer pricing for model ids, e.g. negotiated or private-deployment
|
|
732
|
+
* rates. This layer is separate from the runtime catalog and imported layers:
|
|
733
|
+
* `_setPricingOverrides`, `hydratePricingCatalog`, and
|
|
734
|
+
* `importResolvedModelCatalog` never reset it; only
|
|
735
|
+
* `clearRegisteredModelPricing` does.
|
|
736
|
+
*
|
|
737
|
+
* Keys are trimmed, lowercased, and stripped of `[1m]`, like the other tables.
|
|
738
|
+
* Matching is exact, never by prefix, against the looked-up id, its registered
|
|
739
|
+
* alias target, and its `canonicalModelId()` form — so a `claude-opus-5-5`
|
|
740
|
+
* entry also answers `claude-opus-5-5-20261001` and
|
|
741
|
+
* `us.anthropic.claude-opus-5-5-20261001-v1:0`.
|
|
742
|
+
*
|
|
743
|
+
* Successive calls merge; re-registering a key replaces its price and mode.
|
|
744
|
+
* Entries whose four rates are not all finite, nonnegative numbers are
|
|
745
|
+
* skipped. Results carry `provenance.source: 'registered'`.
|
|
746
|
+
*
|
|
747
|
+
* Registered entries are realm-local configuration and are kept out of
|
|
748
|
+
* cross-realm snapshots: they are not listed by the catalog id enumeration,
|
|
749
|
+
* and `resolveModelPricing(id, { includeRegistered: false })` is the
|
|
750
|
+
* resolution a snapshot should carry. An exported price would otherwise be
|
|
751
|
+
* pinned as an exact import in the other realm and outlive
|
|
752
|
+
* `clearRegisteredModelPricing` there. Register in each realm instead.
|
|
753
|
+
*/
|
|
754
|
+
function registerModelPricing(entries, options) {
|
|
755
|
+
const onlyIfMissing = options?.onlyIfMissing === true;
|
|
756
|
+
let changed = false;
|
|
757
|
+
for (const [key, value] of Object.entries(entries ?? {})) {
|
|
758
|
+
const id = key
|
|
759
|
+
.replace(/\[1m\]/gi, '')
|
|
760
|
+
.trim()
|
|
761
|
+
.toLowerCase();
|
|
762
|
+
if (!id || !isValidModelPricing(value))
|
|
763
|
+
continue;
|
|
764
|
+
registeredTable ??= new Map();
|
|
765
|
+
registeredTable.set(id, {
|
|
766
|
+
pricing: {
|
|
767
|
+
inputCostPerMillion: value.inputCostPerMillion,
|
|
768
|
+
outputCostPerMillion: value.outputCostPerMillion,
|
|
769
|
+
cacheWriteCostPerMillion: value.cacheWriteCostPerMillion,
|
|
770
|
+
cacheReadCostPerMillion: value.cacheReadCostPerMillion,
|
|
771
|
+
},
|
|
772
|
+
onlyIfMissing,
|
|
773
|
+
});
|
|
774
|
+
changed = true;
|
|
775
|
+
}
|
|
776
|
+
if (changed)
|
|
777
|
+
invalidatePricingDigest();
|
|
778
|
+
}
|
|
779
|
+
/** Remove every entry added by `registerModelPricing`. */
|
|
780
|
+
function clearRegisteredModelPricing() {
|
|
781
|
+
if (registeredTable && registeredTable.size > 0)
|
|
782
|
+
invalidatePricingDigest();
|
|
783
|
+
registeredTable = null;
|
|
784
|
+
}
|
|
785
|
+
function findRegisteredPricing(rawId, aliasId) {
|
|
786
|
+
if (!registeredTable || registeredTable.size === 0)
|
|
787
|
+
return null;
|
|
788
|
+
const canonical = canonicalModelId(rawId);
|
|
789
|
+
for (const id of [rawId, aliasId, canonical, (0, modelAliases_1.resolveModelAlias)(canonical)]) {
|
|
790
|
+
const entry = registeredTable.get(id);
|
|
791
|
+
if (entry)
|
|
792
|
+
return { id, entry };
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
// ── Pricing Basis ──
|
|
797
|
+
let pricingDigestMemo = null;
|
|
798
|
+
function invalidatePricingDigest() {
|
|
799
|
+
pricingDigestMemo = null;
|
|
800
|
+
}
|
|
801
|
+
/** JSON with object keys sorted at every level; `undefined` members are dropped. */
|
|
802
|
+
function canonicalJson(value) {
|
|
803
|
+
if (value === null || typeof value !== 'object') {
|
|
804
|
+
return JSON.stringify(value) ?? 'null';
|
|
805
|
+
}
|
|
806
|
+
if (Array.isArray(value))
|
|
807
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
808
|
+
const record = value;
|
|
809
|
+
const keys = Object.keys(record)
|
|
810
|
+
.filter((key) => record[key] !== undefined)
|
|
811
|
+
.sort();
|
|
812
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* 64-bit FNV-1a over the UTF-8 bytes of `text`, as 16 lowercase hex digits.
|
|
816
|
+
* Uses four 16-bit limbs so it runs on plain numbers in every JS runtime.
|
|
817
|
+
*/
|
|
818
|
+
function fnv1a64Hex(text) {
|
|
819
|
+
// Offset basis 0xcbf29ce484222325, least significant limb first.
|
|
820
|
+
let h0 = 0x2325;
|
|
821
|
+
let h1 = 0x8422;
|
|
822
|
+
let h2 = 0x9ce4;
|
|
823
|
+
let h3 = 0xcbf2;
|
|
824
|
+
const mix = (byte) => {
|
|
825
|
+
h0 ^= byte;
|
|
826
|
+
// Multiply by the FNV prime 0x100000001b3 = 2^40 + 0x1b3, mod 2^64.
|
|
827
|
+
const t0 = h0 * 0x1b3;
|
|
828
|
+
const t1 = h1 * 0x1b3 + (t0 >>> 16);
|
|
829
|
+
const t2 = h2 * 0x1b3 + (h0 << 8) + (t1 >>> 16);
|
|
830
|
+
const t3 = h3 * 0x1b3 + (h1 << 8) + (t2 >>> 16);
|
|
831
|
+
h0 = t0 & 0xffff;
|
|
832
|
+
h1 = t1 & 0xffff;
|
|
833
|
+
h2 = t2 & 0xffff;
|
|
834
|
+
h3 = t3 & 0xffff;
|
|
835
|
+
};
|
|
836
|
+
for (let i = 0; i < text.length; i++) {
|
|
837
|
+
let code = text.charCodeAt(i);
|
|
838
|
+
if (code >= 0xd800 && code <= 0xdfff) {
|
|
839
|
+
const low = code <= 0xdbff && i + 1 < text.length ? text.charCodeAt(i + 1) : 0;
|
|
840
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
841
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
842
|
+
i++;
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
// A lone surrogate encodes as U+FFFD, as TextEncoder does.
|
|
846
|
+
code = 0xfffd;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (code < 0x80) {
|
|
850
|
+
mix(code);
|
|
851
|
+
}
|
|
852
|
+
else if (code < 0x800) {
|
|
853
|
+
mix(0xc0 | (code >> 6));
|
|
854
|
+
mix(0x80 | (code & 0x3f));
|
|
855
|
+
}
|
|
856
|
+
else if (code < 0x10000) {
|
|
857
|
+
mix(0xe0 | (code >> 12));
|
|
858
|
+
mix(0x80 | ((code >> 6) & 0x3f));
|
|
859
|
+
mix(0x80 | (code & 0x3f));
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
mix(0xf0 | (code >> 18));
|
|
863
|
+
mix(0x80 | ((code >> 12) & 0x3f));
|
|
864
|
+
mix(0x80 | ((code >> 6) & 0x3f));
|
|
865
|
+
mix(0x80 | (code & 0x3f));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return [h3, h2, h1, h0].map((limb) => limb.toString(16).padStart(4, '0')).join('');
|
|
869
|
+
}
|
|
870
|
+
function getPricingDigest() {
|
|
871
|
+
if (pricingDigestMemo === null) {
|
|
872
|
+
const imported = {};
|
|
873
|
+
for (const [id, entry] of Object.entries(importedTable)) {
|
|
874
|
+
imported[id] = { pricing: entry.pricing, provenance: entry.provenance };
|
|
875
|
+
}
|
|
876
|
+
const registered = {};
|
|
877
|
+
for (const [id, entry] of registeredTable ?? []) {
|
|
878
|
+
registered[id] = { pricing: entry.pricing, onlyIfMissing: entry.onlyIfMissing };
|
|
879
|
+
}
|
|
880
|
+
const payload = canonicalJson({
|
|
881
|
+
v: 1,
|
|
882
|
+
static: PRICING_TABLE,
|
|
883
|
+
catalog: overrideTable,
|
|
884
|
+
imported,
|
|
885
|
+
registered,
|
|
886
|
+
});
|
|
887
|
+
pricingDigestMemo = `fnv1a64:${fnv1a64Hex(payload)}`;
|
|
888
|
+
}
|
|
889
|
+
return pricingDigestMemo;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Describe the pricing basis behind current cost estimates: which layer the
|
|
893
|
+
* prices come from, when the catalog was fetched, and a digest that changes
|
|
894
|
+
* whenever any active price changes. Pure and cheap after the first call (the
|
|
895
|
+
* digest is memoized until a table changes).
|
|
896
|
+
*/
|
|
897
|
+
function getPricingCatalogInfo() {
|
|
898
|
+
const catalogEntries = overrideSortedKeys.length;
|
|
899
|
+
let source = 'static';
|
|
900
|
+
if (catalogEntries > 0) {
|
|
901
|
+
source = overrideBasis?.source ?? 'litellm';
|
|
902
|
+
}
|
|
903
|
+
else if (Object.keys(importedTable).length > 0) {
|
|
904
|
+
source = 'imported';
|
|
905
|
+
}
|
|
906
|
+
const fetchedAt = catalogEntries > 0 ? overrideBasis?.fetchedAt : undefined;
|
|
907
|
+
return {
|
|
908
|
+
source,
|
|
909
|
+
...(fetchedAt ? { fetchedAt } : {}),
|
|
910
|
+
digest: getPricingDigest(),
|
|
911
|
+
catalogEntries,
|
|
912
|
+
registeredEntries: registeredTable?.size ?? 0,
|
|
913
|
+
};
|
|
683
914
|
}
|
|
684
915
|
/** Internal: list every ID known to the pricing resolver. */
|
|
685
916
|
function _getModelPricingIds() {
|
|
@@ -689,6 +920,86 @@ function _getModelPricingIds() {
|
|
|
689
920
|
...Object.keys(importedTable),
|
|
690
921
|
]));
|
|
691
922
|
}
|
|
923
|
+
// ── Model ID Canonicalization ──
|
|
924
|
+
// Bedrock: optional cross-region prefix (`us.`, `eu.`, `apac.`, `global.`, …)
|
|
925
|
+
// then the vendor segment, e.g. `us.anthropic.claude-opus-5-5-20261001-v1:0`.
|
|
926
|
+
const BEDROCK_ANTHROPIC_RE = /^(?:[a-z]{2,6}(?:-[a-z]{2,4})?\.)?anthropic\./;
|
|
927
|
+
// Bedrock revision tag (`-v1:0`, `-v1`), only after a version or date digit so
|
|
928
|
+
// legacy names such as `claude-v2:1` and `claude-instant-v1` keep theirs.
|
|
929
|
+
const BEDROCK_REVISION_RE = /([0-9])-v[0-9]+(?::[0-9]+)?$/;
|
|
930
|
+
const VERTEX_VERSION_RE = /@(?:[0-9]{8}|latest)$/;
|
|
931
|
+
const LATEST_SUFFIX_RE = /-latest$/;
|
|
932
|
+
const DASHED_DATE_SUFFIX_RE = /-[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
933
|
+
const COMPACT_DATE_SUFFIX_RE = /-[0-9]{8}$/;
|
|
934
|
+
/**
|
|
935
|
+
* Reduce a model id to the bare model name it denotes, for grouping and
|
|
936
|
+
* matching across hosts and snapshots.
|
|
937
|
+
*
|
|
938
|
+
* Trims and lowercases, then removes: `[1m]`; every leading `segment/`
|
|
939
|
+
* provider or region prefix (`azure/us/gpt-6.1-terra` → `gpt-6.1-terra`);
|
|
940
|
+
* Bedrock forms (`us.anthropic.claude-opus-5-5-20261001-v1:0` →
|
|
941
|
+
* `claude-opus-5-5`, also `eu.`/`apac.`/`global.` and bare `anthropic.`);
|
|
942
|
+
* Vertex `@YYYYMMDD`; trailing `-YYYYMMDD` and `-YYYY-MM-DD` dates; `-latest`.
|
|
943
|
+
* Meaningful suffixes (`-mini`, `-codex`, `-sol`, `-terra`, `-high`, …) stay.
|
|
944
|
+
*
|
|
945
|
+
* Pure and syntactic: registered aliases are not applied (see
|
|
946
|
+
* `resolveModelAlias`). Pricing lookups do not use this to reach other
|
|
947
|
+
* models' prices — dated snapshots and deployments can bill differently — only
|
|
948
|
+
* to match `registerModelPricing` entries.
|
|
949
|
+
*/
|
|
950
|
+
function canonicalModelId(id) {
|
|
951
|
+
let value = String(id ?? '')
|
|
952
|
+
.trim()
|
|
953
|
+
.toLowerCase()
|
|
954
|
+
.replace(/\[1m\]/g, '')
|
|
955
|
+
.trim();
|
|
956
|
+
const slash = value.lastIndexOf('/');
|
|
957
|
+
if (slash >= 0 && slash < value.length - 1)
|
|
958
|
+
value = value.slice(slash + 1);
|
|
959
|
+
const bedrock = BEDROCK_ANTHROPIC_RE.exec(value);
|
|
960
|
+
if (bedrock && value.length > bedrock[0].length) {
|
|
961
|
+
value = value.slice(bedrock[0].length).replace(BEDROCK_REVISION_RE, '$1');
|
|
962
|
+
}
|
|
963
|
+
// Suffixes can stack (`-20250101-latest`), so strip until nothing changes.
|
|
964
|
+
for (;;) {
|
|
965
|
+
const next = value
|
|
966
|
+
.replace(VERTEX_VERSION_RE, '')
|
|
967
|
+
.replace(LATEST_SUFFIX_RE, '')
|
|
968
|
+
.replace(DASHED_DATE_SUFFIX_RE, '')
|
|
969
|
+
.replace(COMPACT_DATE_SUFFIX_RE, '');
|
|
970
|
+
if (next === value || !next)
|
|
971
|
+
break;
|
|
972
|
+
value = next;
|
|
973
|
+
}
|
|
974
|
+
return value;
|
|
975
|
+
}
|
|
976
|
+
// Names an agent CLI re-points at a different concrete model over time.
|
|
977
|
+
// An object literal rather than a Set keeps it free for tree-shaking.
|
|
978
|
+
const MOVING_MODEL_ALIASES = {
|
|
979
|
+
default: true,
|
|
980
|
+
auto: true,
|
|
981
|
+
latest: true,
|
|
982
|
+
sonnet: true,
|
|
983
|
+
opus: true,
|
|
984
|
+
haiku: true,
|
|
985
|
+
fable: true,
|
|
986
|
+
opusplan: true,
|
|
987
|
+
};
|
|
988
|
+
/**
|
|
989
|
+
* True for model names an agent CLI resolves to a different concrete model
|
|
990
|
+
* over time (`default`, `auto`, `latest`, `sonnet`, `opus`, `haiku`, `fable`,
|
|
991
|
+
* and Claude Code's `opusplan`), so a recorded value names a moving target
|
|
992
|
+
* rather than a model. Case-insensitive, trimmed, and `[1m]`-tolerant
|
|
993
|
+
* (`sonnet[1m]`). Dated or versioned ids are never moving aliases.
|
|
994
|
+
*/
|
|
995
|
+
function isMovingModelAlias(id) {
|
|
996
|
+
const value = String(id ?? '')
|
|
997
|
+
.trim()
|
|
998
|
+
.toLowerCase()
|
|
999
|
+
.replace(/\[1m\]/g, '')
|
|
1000
|
+
.trim();
|
|
1001
|
+
return Object.prototype.hasOwnProperty.call(MOVING_MODEL_ALIASES, value);
|
|
1002
|
+
}
|
|
692
1003
|
// ── Model ID Parsing ──
|
|
693
1004
|
// Anthropic writes the minor version with a hyphen in modern IDs
|
|
694
1005
|
// (`claude-sonnet-4-6-20260321`) and a dot in older ones
|
|
@@ -700,6 +1011,14 @@ const LEGACY_CLAUDE_RE = /^claude-([0-9]+(?:[-.][0-9]+)?)-(haiku|sonnet|opus)(?:
|
|
|
700
1011
|
const GPT_RE = /^gpt-([0-9][0-9.A-Za-z-]*)/i;
|
|
701
1012
|
const O_SERIES_RE = /^o([0-9]+)(-mini|-pro)?/i;
|
|
702
1013
|
const GEMINI_RE = /^gemini-([0-9][0-9.A-Za-z-]*)/i;
|
|
1014
|
+
// GPT tier names, read from the parsed version (`6.1-sol`, `6-luna-2026-01-01`).
|
|
1015
|
+
const GPT_TIER_VARIANT_RE = /^[0-9]+(?:\.[0-9]+)*-(astra|sol|terra|luna)(?=-|$)/;
|
|
1016
|
+
const GPT_TIER_VARIANT_RANK = {
|
|
1017
|
+
astra: 0,
|
|
1018
|
+
sol: 1,
|
|
1019
|
+
terra: 2,
|
|
1020
|
+
luna: 3,
|
|
1021
|
+
};
|
|
703
1022
|
/**
|
|
704
1023
|
* Parses a model ID into {provider, family, version}.
|
|
705
1024
|
*
|
|
@@ -731,7 +1050,13 @@ function parseModelId(modelId) {
|
|
|
731
1050
|
}
|
|
732
1051
|
const gpt = normalized.match(GPT_RE);
|
|
733
1052
|
if (gpt) {
|
|
734
|
-
|
|
1053
|
+
const version = gpt[1];
|
|
1054
|
+
const variant = GPT_TIER_VARIANT_RE.exec(version)?.[1];
|
|
1055
|
+
// The key is only present when set, so untiered results stay deep-equal
|
|
1056
|
+
// to what earlier releases returned.
|
|
1057
|
+
return variant
|
|
1058
|
+
? { provider: 'openai', family: 'gpt', version, variant }
|
|
1059
|
+
: { provider: 'openai', family: 'gpt', version };
|
|
735
1060
|
}
|
|
736
1061
|
const oSeries = normalized.match(O_SERIES_RE);
|
|
737
1062
|
if (oSeries) {
|
|
@@ -753,24 +1078,45 @@ function findLongestPrefix(keys, modelId) {
|
|
|
753
1078
|
}
|
|
754
1079
|
return null;
|
|
755
1080
|
}
|
|
756
|
-
/**
|
|
757
|
-
|
|
1081
|
+
/**
|
|
1082
|
+
* Resolve pricing and disclose which layer answered and whether the result was
|
|
1083
|
+
* inherited by prefix.
|
|
1084
|
+
*
|
|
1085
|
+
* Order: registered entries (default mode); exact imported, catalog, and
|
|
1086
|
+
* static matches; registered `onlyIfMissing` entries; catalog then static
|
|
1087
|
+
* prefix matches; unpriced.
|
|
1088
|
+
*/
|
|
1089
|
+
function resolveModelPricing(modelId, options) {
|
|
758
1090
|
const stripped = modelId.replace(/\[1m\]/gi, '');
|
|
759
1091
|
const rawId = stripped.trim().toLowerCase();
|
|
760
|
-
const
|
|
1092
|
+
const aliasId = (0, modelAliases_1.resolveModelAlias)(stripped);
|
|
761
1093
|
// The raw id is tried before its alias target at every step: pricing
|
|
762
1094
|
// recorded for the alias id itself is more specific than the canonical
|
|
763
1095
|
// fallback, so registering an alias never orphans it.
|
|
764
|
-
const candidateIds = rawId ===
|
|
765
|
-
const
|
|
1096
|
+
const candidateIds = rawId === aliasId ? [aliasId] : [rawId, aliasId];
|
|
1097
|
+
const registered = options?.includeRegistered === false ? null : findRegisteredPricing(rawId, aliasId);
|
|
1098
|
+
const registeredResult = (match) => ({
|
|
1099
|
+
modelId,
|
|
1100
|
+
canonicalModelId: aliasId,
|
|
1101
|
+
pricing: { ...match.entry.pricing },
|
|
1102
|
+
provenance: {
|
|
1103
|
+
source: 'registered',
|
|
1104
|
+
match: 'exact',
|
|
1105
|
+
matchedModelId: match.id,
|
|
1106
|
+
inheritedByPrefix: false,
|
|
1107
|
+
},
|
|
1108
|
+
});
|
|
1109
|
+
if (registered && !registered.entry.onlyIfMissing)
|
|
1110
|
+
return registeredResult(registered);
|
|
1111
|
+
const imported = importedTable[rawId] ?? importedTable[aliasId];
|
|
766
1112
|
if (imported)
|
|
767
|
-
return { ...imported, modelId, canonicalModelId };
|
|
1113
|
+
return { ...imported, modelId, canonicalModelId: aliasId };
|
|
768
1114
|
for (const id of candidateIds) {
|
|
769
1115
|
const exactOverride = overrideTable[id];
|
|
770
1116
|
if (exactOverride) {
|
|
771
1117
|
return {
|
|
772
1118
|
modelId,
|
|
773
|
-
canonicalModelId,
|
|
1119
|
+
canonicalModelId: aliasId,
|
|
774
1120
|
pricing: exactOverride,
|
|
775
1121
|
provenance: {
|
|
776
1122
|
source: 'catalog',
|
|
@@ -786,7 +1132,7 @@ function resolveModelPricing(modelId) {
|
|
|
786
1132
|
if (exactStatic) {
|
|
787
1133
|
return {
|
|
788
1134
|
modelId,
|
|
789
|
-
canonicalModelId,
|
|
1135
|
+
canonicalModelId: aliasId,
|
|
790
1136
|
pricing: exactStatic,
|
|
791
1137
|
provenance: {
|
|
792
1138
|
source: 'static',
|
|
@@ -797,12 +1143,15 @@ function resolveModelPricing(modelId) {
|
|
|
797
1143
|
};
|
|
798
1144
|
}
|
|
799
1145
|
}
|
|
1146
|
+
// A gap-filling registration answers once no layer has an exact price.
|
|
1147
|
+
if (registered)
|
|
1148
|
+
return registeredResult(registered);
|
|
800
1149
|
for (const id of candidateIds) {
|
|
801
1150
|
const overridePrefix = findLongestPrefix(overrideSortedKeys, id);
|
|
802
1151
|
if (overridePrefix) {
|
|
803
1152
|
return {
|
|
804
1153
|
modelId,
|
|
805
|
-
canonicalModelId,
|
|
1154
|
+
canonicalModelId: aliasId,
|
|
806
1155
|
pricing: overrideTable[overridePrefix],
|
|
807
1156
|
provenance: {
|
|
808
1157
|
source: 'catalog',
|
|
@@ -814,11 +1163,11 @@ function resolveModelPricing(modelId) {
|
|
|
814
1163
|
}
|
|
815
1164
|
}
|
|
816
1165
|
for (const id of candidateIds) {
|
|
817
|
-
const staticPrefix = findLongestPrefix(
|
|
1166
|
+
const staticPrefix = findLongestPrefix(getStaticSortedKeys(), id);
|
|
818
1167
|
if (staticPrefix) {
|
|
819
1168
|
return {
|
|
820
1169
|
modelId,
|
|
821
|
-
canonicalModelId,
|
|
1170
|
+
canonicalModelId: aliasId,
|
|
822
1171
|
pricing: PRICING_TABLE[staticPrefix],
|
|
823
1172
|
provenance: {
|
|
824
1173
|
source: 'static',
|
|
@@ -831,7 +1180,7 @@ function resolveModelPricing(modelId) {
|
|
|
831
1180
|
}
|
|
832
1181
|
return {
|
|
833
1182
|
modelId,
|
|
834
|
-
canonicalModelId,
|
|
1183
|
+
canonicalModelId: aliasId,
|
|
835
1184
|
pricing: null,
|
|
836
1185
|
provenance: { source: 'none', match: 'none', inheritedByPrefix: false },
|
|
837
1186
|
};
|
|
@@ -839,9 +1188,11 @@ function resolveModelPricing(modelId) {
|
|
|
839
1188
|
/**
|
|
840
1189
|
* Gets pricing for a model ID.
|
|
841
1190
|
*
|
|
842
|
-
* Lookup order:
|
|
843
|
-
* 1.
|
|
844
|
-
* 2.
|
|
1191
|
+
* Lookup order (see `resolveModelPricing` for provenance):
|
|
1192
|
+
* 1. Registered entries (`registerModelPricing`), exact matches only.
|
|
1193
|
+
* 2. Imported resolutions, then the runtime override map (from LiteLLM
|
|
1194
|
+
* catalog hydration), then the static PRICING_TABLE — exact before prefix,
|
|
1195
|
+
* with gap-filling registered entries between the two.
|
|
845
1196
|
* 3. `null` — unknown model. Callers MUST handle this.
|
|
846
1197
|
*
|
|
847
1198
|
* The ID is first looked up verbatim (minus the "[1m]" suffix) — override
|
|
@@ -933,22 +1284,47 @@ function mergeCostSources(a, b) {
|
|
|
933
1284
|
};
|
|
934
1285
|
return rank[a] >= rank[b] ? a : b;
|
|
935
1286
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1287
|
+
/**
|
|
1288
|
+
* Map the provenance `calculateNormalizedUsageCost` reports to the display
|
|
1289
|
+
* cost source: provider-reported costs are `reported`, costs computed from
|
|
1290
|
+
* explicit or catalog pricing are `estimated`, and unpriced stays `unpriced`.
|
|
1291
|
+
*/
|
|
1292
|
+
function costSourceFromPricingProvenance(provenance) {
|
|
1293
|
+
switch (provenance) {
|
|
1294
|
+
case 'provider-reported':
|
|
1295
|
+
return 'reported';
|
|
1296
|
+
case 'explicit-pricing':
|
|
1297
|
+
case 'model-catalog':
|
|
1298
|
+
return 'estimated';
|
|
1299
|
+
default:
|
|
1300
|
+
return 'unpriced';
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Merge the cost sources of a rollup's components, least certain winning (as
|
|
1305
|
+
* `mergeCostSources`). Components without a `source` have not been seen yet
|
|
1306
|
+
* and are ignored, and an `unpriced` component that contributed no tokens
|
|
1307
|
+
* does not taint the total. Returns `undefined` when nothing counted.
|
|
1308
|
+
*/
|
|
1309
|
+
function mergeCostComponents(components) {
|
|
1310
|
+
let merged;
|
|
1311
|
+
for (const component of components) {
|
|
1312
|
+
const source = component?.source;
|
|
1313
|
+
if (!source)
|
|
1314
|
+
continue;
|
|
1315
|
+
if (source === 'unpriced' && !(component.tokens > 0))
|
|
1316
|
+
continue;
|
|
1317
|
+
merged = merged ? mergeCostSources(merged, source) : source;
|
|
1318
|
+
}
|
|
1319
|
+
return merged;
|
|
945
1320
|
}
|
|
1321
|
+
// ── Display ──
|
|
946
1322
|
/**
|
|
947
1323
|
* Short display label for model IDs. Keeps historical Claude family labels
|
|
948
1324
|
* compact while normalizing common OpenAI labels.
|
|
949
1325
|
*/
|
|
950
1326
|
function shortModelName(modelId) {
|
|
951
|
-
const normalized =
|
|
1327
|
+
const normalized = canonicalModelId(modelId);
|
|
952
1328
|
const parsed = parseModelId(normalized);
|
|
953
1329
|
if (normalized.includes('codex'))
|
|
954
1330
|
return 'Codex';
|
|
@@ -987,7 +1363,7 @@ function versionRank(version) {
|
|
|
987
1363
|
*/
|
|
988
1364
|
function getModelDisplayInfo(modelId) {
|
|
989
1365
|
const parsed = parseModelId(modelId);
|
|
990
|
-
const normalized =
|
|
1366
|
+
const normalized = canonicalModelId(modelId);
|
|
991
1367
|
let rank = 1_000;
|
|
992
1368
|
if (parsed?.provider === 'anthropic') {
|
|
993
1369
|
rank = (CLAUDE_FAMILY_RANK[parsed.family] ?? 9) * 100 + versionRank(parsed.version);
|
|
@@ -1013,29 +1389,37 @@ function getModelDisplayInfo(modelId) {
|
|
|
1013
1389
|
rank,
|
|
1014
1390
|
};
|
|
1015
1391
|
}
|
|
1016
|
-
/**
|
|
1392
|
+
/**
|
|
1393
|
+
* Order two GPT tier variants of the same family and numeric version
|
|
1394
|
+
* (astra > sol > terra > luna); 0 for every other pair.
|
|
1395
|
+
*/
|
|
1396
|
+
function compareTierVariants(a, b) {
|
|
1397
|
+
const left = parseModelId(a);
|
|
1398
|
+
const right = parseModelId(b);
|
|
1399
|
+
if (!left?.variant || !right?.variant)
|
|
1400
|
+
return 0;
|
|
1401
|
+
if (left.provider !== right.provider || left.family !== right.family)
|
|
1402
|
+
return 0;
|
|
1403
|
+
if (versionRank(left.version) !== versionRank(right.version))
|
|
1404
|
+
return 0;
|
|
1405
|
+
return ((GPT_TIER_VARIANT_RANK[left.variant] ?? Number.MAX_SAFE_INTEGER) -
|
|
1406
|
+
(GPT_TIER_VARIANT_RANK[right.variant] ?? Number.MAX_SAFE_INTEGER));
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Compare two model IDs using shared provider/family ranking rules. Within a
|
|
1410
|
+
* family and numeric version, GPT tiers rank astra > sol > terra > luna.
|
|
1411
|
+
*/
|
|
1017
1412
|
function compareModelIds(a, b) {
|
|
1018
1413
|
const left = getModelDisplayInfo(a);
|
|
1019
1414
|
const right = getModelDisplayInfo(b);
|
|
1020
1415
|
if (left.rank !== right.rank)
|
|
1021
1416
|
return left.rank - right.rank;
|
|
1417
|
+
const variantOrder = compareTierVariants(a, b);
|
|
1418
|
+
if (variantOrder !== 0)
|
|
1419
|
+
return variantOrder;
|
|
1022
1420
|
return a.localeCompare(b);
|
|
1023
1421
|
}
|
|
1024
1422
|
/** Return a sorted copy of model IDs using shared provider/family ranking rules. */
|
|
1025
1423
|
function sortModelIds(modelIds) {
|
|
1026
1424
|
return [...modelIds].sort(compareModelIds);
|
|
1027
1425
|
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Formats a USD cost as a currency string.
|
|
1030
|
-
*
|
|
1031
|
-
* - `null` / `undefined` → `"—"` (honest "pricing unavailable").
|
|
1032
|
-
* - `< $0.01` → 4 decimals, so sub-cent costs are still visible.
|
|
1033
|
-
* - otherwise → 2 decimals.
|
|
1034
|
-
*/
|
|
1035
|
-
function formatCost(cost) {
|
|
1036
|
-
if (cost === null || cost === undefined)
|
|
1037
|
-
return '—';
|
|
1038
|
-
if (cost < 0.01)
|
|
1039
|
-
return `$${cost.toFixed(4)}`;
|
|
1040
|
-
return `$${cost.toFixed(2)}`;
|
|
1041
|
-
}
|
|
@@ -293,7 +293,7 @@ class MultiProviderQuotaService {
|
|
|
293
293
|
await this.refreshPeakHours();
|
|
294
294
|
const creds = await this.readClaudeCredentials();
|
|
295
295
|
if (!creds) {
|
|
296
|
-
this.handleUnavailableState(makeUnavailableClaudeState('Renew your Claude
|
|
296
|
+
this.handleUnavailableState(makeUnavailableClaudeState('Renew your Claude login to view quota', 'auth'));
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
299
299
|
const state = await this.fetchClaudeQuota(creds.accessToken);
|