sidekick-shared 0.27.6 → 0.28.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 +28 -0
- package/README.md +58 -18
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +2 -9
- package/dist/claudeProfiles.js +10 -25
- package/dist/codexPaths.d.ts +22 -0
- package/dist/codexPaths.js +67 -0
- package/dist/codexProfiles.d.ts +1 -4
- package/dist/codexProfiles.js +5 -43
- package/dist/doctor.js +2 -2
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +49 -15
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model identification, pricing, and metadata.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for:
|
|
5
|
+
* - Parsing model IDs into {provider, family, version}
|
|
6
|
+
* - Looking up per-token pricing (static baseline + optional runtime overrides)
|
|
7
|
+
* - Calculating USD cost from token usage
|
|
8
|
+
*
|
|
9
|
+
* Design notes:
|
|
10
|
+
* - Pricing lookup is honest: unknown models return `null`, never a "best-guess
|
|
11
|
+
* other model's rate". Callers must handle null and render "—" in UIs.
|
|
12
|
+
* - An override map (populated by `pricingCatalog.ts` via `_setPricingOverrides`)
|
|
13
|
+
* is consulted first, so runtime hydration from LiteLLM supersedes the static
|
|
14
|
+
* baseline without requiring a redeploy when vendor prices change.
|
|
15
|
+
* - Longest-prefix matching handles variants like `claude-opus-4.5-20250514`
|
|
16
|
+
* against a key like `claude-opus-4.5`. Same pattern used in modelContext.ts.
|
|
17
|
+
*
|
|
18
|
+
* @module modelInfo
|
|
19
|
+
*/
|
|
20
|
+
import { getModelContextWindowSize } from './modelContext';
|
|
21
|
+
import { resolveModelAlias } from './modelAliases';
|
|
22
|
+
export { MODEL_PROVIDER_IDS } from './providerIds';
|
|
23
|
+
// ── Static Pricing Table ──
|
|
24
|
+
/**
|
|
25
|
+
* Static baseline pricing. Keys are model-ID prefixes; lookup uses
|
|
26
|
+
* longest-prefix matching so `claude-sonnet-4.5-20250514` resolves
|
|
27
|
+
* against `claude-sonnet-4.5`.
|
|
28
|
+
*
|
|
29
|
+
* Sources:
|
|
30
|
+
* - Anthropic: https://platform.claude.com/docs/en/about-claude/pricing
|
|
31
|
+
* - OpenAI: https://developers.openai.com/api/docs/models
|
|
32
|
+
* Current Anthropic and GPT-5.6/6 rates verified: 2026-09-22.
|
|
33
|
+
* Runtime LiteLLM hydration refreshes this offline baseline.
|
|
34
|
+
*
|
|
35
|
+
* Anthropic keys appear in both dashed (`claude-opus-4-8`, the real model-ID
|
|
36
|
+
* form) and dotted (`claude-opus-4.8`, the LiteLLM catalog form) spellings —
|
|
37
|
+
* prefix matching cannot bridge the two, so both are needed.
|
|
38
|
+
*/
|
|
39
|
+
const PRICING_TABLE = {
|
|
40
|
+
// ── Anthropic: Claude ──
|
|
41
|
+
// Fable 5.1 cache reads cost 0.025x input; Fable 5 retains its 0.1x rate.
|
|
42
|
+
'claude-fable-5-1': {
|
|
43
|
+
inputCostPerMillion: 10.0,
|
|
44
|
+
outputCostPerMillion: 50.0,
|
|
45
|
+
cacheWriteCostPerMillion: 12.5,
|
|
46
|
+
cacheReadCostPerMillion: 0.25,
|
|
47
|
+
},
|
|
48
|
+
'claude-fable-5.1': {
|
|
49
|
+
inputCostPerMillion: 10.0,
|
|
50
|
+
outputCostPerMillion: 50.0,
|
|
51
|
+
cacheWriteCostPerMillion: 12.5,
|
|
52
|
+
cacheReadCostPerMillion: 0.25,
|
|
53
|
+
},
|
|
54
|
+
'claude-fable-5': {
|
|
55
|
+
inputCostPerMillion: 10.0,
|
|
56
|
+
outputCostPerMillion: 50.0,
|
|
57
|
+
cacheWriteCostPerMillion: 12.5,
|
|
58
|
+
cacheReadCostPerMillion: 1.0,
|
|
59
|
+
},
|
|
60
|
+
'claude-haiku-4-5': {
|
|
61
|
+
inputCostPerMillion: 1.0,
|
|
62
|
+
outputCostPerMillion: 5.0,
|
|
63
|
+
cacheWriteCostPerMillion: 1.25,
|
|
64
|
+
cacheReadCostPerMillion: 0.1,
|
|
65
|
+
},
|
|
66
|
+
'claude-haiku-4.5': {
|
|
67
|
+
inputCostPerMillion: 1.0,
|
|
68
|
+
outputCostPerMillion: 5.0,
|
|
69
|
+
cacheWriteCostPerMillion: 1.25,
|
|
70
|
+
cacheReadCostPerMillion: 0.1,
|
|
71
|
+
},
|
|
72
|
+
'claude-haiku-3.5': {
|
|
73
|
+
inputCostPerMillion: 0.8,
|
|
74
|
+
outputCostPerMillion: 4.0,
|
|
75
|
+
cacheWriteCostPerMillion: 1.0,
|
|
76
|
+
cacheReadCostPerMillion: 0.08,
|
|
77
|
+
},
|
|
78
|
+
// Sonnet 5's $2/$10 introductory rate is now permanent. Standard pricing
|
|
79
|
+
// applies across its full 1M context window.
|
|
80
|
+
'claude-sonnet-5': {
|
|
81
|
+
inputCostPerMillion: 2.0,
|
|
82
|
+
outputCostPerMillion: 10.0,
|
|
83
|
+
cacheWriteCostPerMillion: 2.5,
|
|
84
|
+
cacheReadCostPerMillion: 0.2,
|
|
85
|
+
},
|
|
86
|
+
'claude-sonnet-4-6': {
|
|
87
|
+
inputCostPerMillion: 3.0,
|
|
88
|
+
outputCostPerMillion: 15.0,
|
|
89
|
+
cacheWriteCostPerMillion: 3.75,
|
|
90
|
+
cacheReadCostPerMillion: 0.3,
|
|
91
|
+
},
|
|
92
|
+
'claude-sonnet-4.6': {
|
|
93
|
+
inputCostPerMillion: 3.0,
|
|
94
|
+
outputCostPerMillion: 15.0,
|
|
95
|
+
cacheWriteCostPerMillion: 3.75,
|
|
96
|
+
cacheReadCostPerMillion: 0.3,
|
|
97
|
+
},
|
|
98
|
+
'claude-sonnet-4-5': {
|
|
99
|
+
inputCostPerMillion: 3.0,
|
|
100
|
+
outputCostPerMillion: 15.0,
|
|
101
|
+
cacheWriteCostPerMillion: 3.75,
|
|
102
|
+
cacheReadCostPerMillion: 0.3,
|
|
103
|
+
},
|
|
104
|
+
'claude-sonnet-4.5': {
|
|
105
|
+
inputCostPerMillion: 3.0,
|
|
106
|
+
outputCostPerMillion: 15.0,
|
|
107
|
+
cacheWriteCostPerMillion: 3.75,
|
|
108
|
+
cacheReadCostPerMillion: 0.3,
|
|
109
|
+
},
|
|
110
|
+
'claude-sonnet-4': {
|
|
111
|
+
inputCostPerMillion: 3.0,
|
|
112
|
+
outputCostPerMillion: 15.0,
|
|
113
|
+
cacheWriteCostPerMillion: 3.75,
|
|
114
|
+
cacheReadCostPerMillion: 0.3,
|
|
115
|
+
},
|
|
116
|
+
// Opus 5.5 undercuts Opus 5; without its own keys it would prefix-match 'claude-opus-5'.
|
|
117
|
+
'claude-opus-5-5': {
|
|
118
|
+
inputCostPerMillion: 4.0,
|
|
119
|
+
outputCostPerMillion: 20.0,
|
|
120
|
+
cacheWriteCostPerMillion: 5.0,
|
|
121
|
+
cacheReadCostPerMillion: 0.2,
|
|
122
|
+
},
|
|
123
|
+
'claude-opus-5.5': {
|
|
124
|
+
inputCostPerMillion: 4.0,
|
|
125
|
+
outputCostPerMillion: 20.0,
|
|
126
|
+
cacheWriteCostPerMillion: 5.0,
|
|
127
|
+
cacheReadCostPerMillion: 0.2,
|
|
128
|
+
},
|
|
129
|
+
'claude-opus-5': {
|
|
130
|
+
inputCostPerMillion: 5.0,
|
|
131
|
+
outputCostPerMillion: 25.0,
|
|
132
|
+
cacheWriteCostPerMillion: 6.25,
|
|
133
|
+
cacheReadCostPerMillion: 0.5,
|
|
134
|
+
},
|
|
135
|
+
'claude-opus-4-8': {
|
|
136
|
+
inputCostPerMillion: 5.0,
|
|
137
|
+
outputCostPerMillion: 25.0,
|
|
138
|
+
cacheWriteCostPerMillion: 6.25,
|
|
139
|
+
cacheReadCostPerMillion: 0.5,
|
|
140
|
+
},
|
|
141
|
+
'claude-opus-4.8': {
|
|
142
|
+
inputCostPerMillion: 5.0,
|
|
143
|
+
outputCostPerMillion: 25.0,
|
|
144
|
+
cacheWriteCostPerMillion: 6.25,
|
|
145
|
+
cacheReadCostPerMillion: 0.5,
|
|
146
|
+
},
|
|
147
|
+
'claude-opus-4-7': {
|
|
148
|
+
inputCostPerMillion: 5.0,
|
|
149
|
+
outputCostPerMillion: 25.0,
|
|
150
|
+
cacheWriteCostPerMillion: 6.25,
|
|
151
|
+
cacheReadCostPerMillion: 0.5,
|
|
152
|
+
},
|
|
153
|
+
'claude-opus-4.7': {
|
|
154
|
+
inputCostPerMillion: 5.0,
|
|
155
|
+
outputCostPerMillion: 25.0,
|
|
156
|
+
cacheWriteCostPerMillion: 6.25,
|
|
157
|
+
cacheReadCostPerMillion: 0.5,
|
|
158
|
+
},
|
|
159
|
+
'claude-opus-4-6': {
|
|
160
|
+
inputCostPerMillion: 5.0,
|
|
161
|
+
outputCostPerMillion: 25.0,
|
|
162
|
+
cacheWriteCostPerMillion: 6.25,
|
|
163
|
+
cacheReadCostPerMillion: 0.5,
|
|
164
|
+
},
|
|
165
|
+
'claude-opus-4.6': {
|
|
166
|
+
inputCostPerMillion: 5.0,
|
|
167
|
+
outputCostPerMillion: 25.0,
|
|
168
|
+
cacheWriteCostPerMillion: 6.25,
|
|
169
|
+
cacheReadCostPerMillion: 0.5,
|
|
170
|
+
},
|
|
171
|
+
'claude-opus-4-5': {
|
|
172
|
+
inputCostPerMillion: 5.0,
|
|
173
|
+
outputCostPerMillion: 25.0,
|
|
174
|
+
cacheWriteCostPerMillion: 6.25,
|
|
175
|
+
cacheReadCostPerMillion: 0.5,
|
|
176
|
+
},
|
|
177
|
+
'claude-opus-4.5': {
|
|
178
|
+
inputCostPerMillion: 5.0,
|
|
179
|
+
outputCostPerMillion: 25.0,
|
|
180
|
+
cacheWriteCostPerMillion: 6.25,
|
|
181
|
+
cacheReadCostPerMillion: 0.5,
|
|
182
|
+
},
|
|
183
|
+
// Opus 4.0 / 4.1 — pre-4.5 pricing tier
|
|
184
|
+
'claude-opus-4': {
|
|
185
|
+
inputCostPerMillion: 15.0,
|
|
186
|
+
outputCostPerMillion: 75.0,
|
|
187
|
+
cacheWriteCostPerMillion: 18.75,
|
|
188
|
+
cacheReadCostPerMillion: 1.5,
|
|
189
|
+
},
|
|
190
|
+
// ── OpenAI: GPT-4.x family ──
|
|
191
|
+
// The `-tts`, `-transcribe`, and `-realtime-preview` variants also bill above
|
|
192
|
+
// the key they inherit, but no coding agent routes to them, so they are left
|
|
193
|
+
// out rather than carried here. Catalog hydration still prices them correctly.
|
|
194
|
+
'gpt-4.1-mini': {
|
|
195
|
+
inputCostPerMillion: 0.4,
|
|
196
|
+
outputCostPerMillion: 1.6,
|
|
197
|
+
cacheWriteCostPerMillion: 0,
|
|
198
|
+
cacheReadCostPerMillion: 0.1,
|
|
199
|
+
},
|
|
200
|
+
'gpt-4.1-nano': {
|
|
201
|
+
inputCostPerMillion: 0.1,
|
|
202
|
+
outputCostPerMillion: 0.4,
|
|
203
|
+
cacheWriteCostPerMillion: 0,
|
|
204
|
+
cacheReadCostPerMillion: 0.025,
|
|
205
|
+
},
|
|
206
|
+
'gpt-4.1': {
|
|
207
|
+
inputCostPerMillion: 2.0,
|
|
208
|
+
outputCostPerMillion: 8.0,
|
|
209
|
+
cacheWriteCostPerMillion: 0,
|
|
210
|
+
cacheReadCostPerMillion: 0.5,
|
|
211
|
+
},
|
|
212
|
+
'gpt-4o-mini': {
|
|
213
|
+
inputCostPerMillion: 0.15,
|
|
214
|
+
outputCostPerMillion: 0.6,
|
|
215
|
+
cacheWriteCostPerMillion: 0,
|
|
216
|
+
cacheReadCostPerMillion: 0.075,
|
|
217
|
+
},
|
|
218
|
+
// The launch snapshot kept its original $5/$15 when gpt-4o was cut to
|
|
219
|
+
// $2.50/$10, so a pinned id must not read the current rate.
|
|
220
|
+
'gpt-4o-2024-05-13': {
|
|
221
|
+
inputCostPerMillion: 5.0,
|
|
222
|
+
outputCostPerMillion: 15.0,
|
|
223
|
+
cacheWriteCostPerMillion: 0,
|
|
224
|
+
cacheReadCostPerMillion: 0,
|
|
225
|
+
},
|
|
226
|
+
'gpt-4o': {
|
|
227
|
+
inputCostPerMillion: 2.5,
|
|
228
|
+
outputCostPerMillion: 10.0,
|
|
229
|
+
cacheWriteCostPerMillion: 0,
|
|
230
|
+
cacheReadCostPerMillion: 1.25,
|
|
231
|
+
},
|
|
232
|
+
'gpt-4-turbo': {
|
|
233
|
+
inputCostPerMillion: 10.0,
|
|
234
|
+
outputCostPerMillion: 30.0,
|
|
235
|
+
cacheWriteCostPerMillion: 0,
|
|
236
|
+
cacheReadCostPerMillion: 0,
|
|
237
|
+
},
|
|
238
|
+
// ── OpenAI: GPT-5/6 families ──
|
|
239
|
+
// Published rates, cross-checked with the LiteLLM catalog. Runtime hydration
|
|
240
|
+
// still supersedes these; they are the offline baseline.
|
|
241
|
+
//
|
|
242
|
+
// Every tier that ships at a rate of its own needs an entry of its own. Keys
|
|
243
|
+
// are matched longest-first, so a `-mini`/`-nano`/`-pro` variant with no entry
|
|
244
|
+
// silently inherits its base model's rate and reports a wrong number rather
|
|
245
|
+
// than none — `gpt-5-nano` read 25x its true cost this way.
|
|
246
|
+
'gpt-6-astra': {
|
|
247
|
+
inputCostPerMillion: 10.0,
|
|
248
|
+
outputCostPerMillion: 50.0,
|
|
249
|
+
cacheWriteCostPerMillion: 12.5,
|
|
250
|
+
cacheReadCostPerMillion: 1.0,
|
|
251
|
+
},
|
|
252
|
+
'gpt-6-sol': {
|
|
253
|
+
inputCostPerMillion: 2.0,
|
|
254
|
+
outputCostPerMillion: 10.0,
|
|
255
|
+
cacheWriteCostPerMillion: 2.5,
|
|
256
|
+
cacheReadCostPerMillion: 0.2,
|
|
257
|
+
},
|
|
258
|
+
'gpt-6-luna': {
|
|
259
|
+
inputCostPerMillion: 0.1,
|
|
260
|
+
outputCostPerMillion: 0.5,
|
|
261
|
+
cacheWriteCostPerMillion: 0.125,
|
|
262
|
+
cacheReadCostPerMillion: 0.01,
|
|
263
|
+
},
|
|
264
|
+
'gpt-5.6-sol': {
|
|
265
|
+
inputCostPerMillion: 4.0,
|
|
266
|
+
outputCostPerMillion: 20.0,
|
|
267
|
+
cacheWriteCostPerMillion: 5.0,
|
|
268
|
+
cacheReadCostPerMillion: 0.4,
|
|
269
|
+
},
|
|
270
|
+
'gpt-5.6-terra': {
|
|
271
|
+
inputCostPerMillion: 2.0,
|
|
272
|
+
outputCostPerMillion: 12.0,
|
|
273
|
+
cacheWriteCostPerMillion: 2.5,
|
|
274
|
+
cacheReadCostPerMillion: 0.2,
|
|
275
|
+
},
|
|
276
|
+
'gpt-5.6-luna': {
|
|
277
|
+
inputCostPerMillion: 0.2,
|
|
278
|
+
outputCostPerMillion: 1.2,
|
|
279
|
+
cacheWriteCostPerMillion: 0.25,
|
|
280
|
+
cacheReadCostPerMillion: 0.02,
|
|
281
|
+
},
|
|
282
|
+
// The GPT-5.6 alias routes to Sol.
|
|
283
|
+
'gpt-5.6': {
|
|
284
|
+
inputCostPerMillion: 4.0,
|
|
285
|
+
outputCostPerMillion: 20.0,
|
|
286
|
+
cacheWriteCostPerMillion: 5.0,
|
|
287
|
+
cacheReadCostPerMillion: 0.4,
|
|
288
|
+
},
|
|
289
|
+
'gpt-5.5-pro': {
|
|
290
|
+
inputCostPerMillion: 30.0,
|
|
291
|
+
outputCostPerMillion: 180.0,
|
|
292
|
+
cacheWriteCostPerMillion: 0,
|
|
293
|
+
cacheReadCostPerMillion: 3.0,
|
|
294
|
+
},
|
|
295
|
+
'gpt-5.5': {
|
|
296
|
+
inputCostPerMillion: 5.0,
|
|
297
|
+
outputCostPerMillion: 30.0,
|
|
298
|
+
cacheWriteCostPerMillion: 0,
|
|
299
|
+
cacheReadCostPerMillion: 0.5,
|
|
300
|
+
},
|
|
301
|
+
'gpt-5.4-mini': {
|
|
302
|
+
inputCostPerMillion: 0.75,
|
|
303
|
+
outputCostPerMillion: 4.5,
|
|
304
|
+
cacheWriteCostPerMillion: 0,
|
|
305
|
+
cacheReadCostPerMillion: 0.075,
|
|
306
|
+
},
|
|
307
|
+
'gpt-5.4-nano': {
|
|
308
|
+
inputCostPerMillion: 0.2,
|
|
309
|
+
outputCostPerMillion: 1.25,
|
|
310
|
+
cacheWriteCostPerMillion: 0,
|
|
311
|
+
cacheReadCostPerMillion: 0.02,
|
|
312
|
+
},
|
|
313
|
+
'gpt-5.4-pro': {
|
|
314
|
+
inputCostPerMillion: 30.0,
|
|
315
|
+
outputCostPerMillion: 180.0,
|
|
316
|
+
cacheWriteCostPerMillion: 0,
|
|
317
|
+
cacheReadCostPerMillion: 3.0,
|
|
318
|
+
},
|
|
319
|
+
'gpt-5.4': {
|
|
320
|
+
inputCostPerMillion: 2.5,
|
|
321
|
+
outputCostPerMillion: 15.0,
|
|
322
|
+
cacheWriteCostPerMillion: 0,
|
|
323
|
+
cacheReadCostPerMillion: 0.25,
|
|
324
|
+
},
|
|
325
|
+
'gpt-5.3-codex': {
|
|
326
|
+
inputCostPerMillion: 1.75,
|
|
327
|
+
outputCostPerMillion: 14.0,
|
|
328
|
+
cacheWriteCostPerMillion: 0,
|
|
329
|
+
cacheReadCostPerMillion: 0.175,
|
|
330
|
+
},
|
|
331
|
+
'gpt-5.3-chat-latest': {
|
|
332
|
+
inputCostPerMillion: 1.75,
|
|
333
|
+
outputCostPerMillion: 14.0,
|
|
334
|
+
cacheWriteCostPerMillion: 0,
|
|
335
|
+
cacheReadCostPerMillion: 0.175,
|
|
336
|
+
},
|
|
337
|
+
// No bare `gpt-5.3` ships in the catalog; both real variants above are
|
|
338
|
+
// $1.75/$14. This entry stays an estimate and only answers ids that match
|
|
339
|
+
// neither variant.
|
|
340
|
+
'gpt-5.3': {
|
|
341
|
+
inputCostPerMillion: 1.25,
|
|
342
|
+
outputCostPerMillion: 10.0,
|
|
343
|
+
cacheWriteCostPerMillion: 0,
|
|
344
|
+
cacheReadCostPerMillion: 0.125,
|
|
345
|
+
},
|
|
346
|
+
'gpt-5.2-pro': {
|
|
347
|
+
inputCostPerMillion: 21.0,
|
|
348
|
+
outputCostPerMillion: 168.0,
|
|
349
|
+
cacheWriteCostPerMillion: 0,
|
|
350
|
+
cacheReadCostPerMillion: 0,
|
|
351
|
+
},
|
|
352
|
+
// Also answers `gpt-5.2-codex` and `gpt-5.2-chat*`, which bill at this rate.
|
|
353
|
+
'gpt-5.2': {
|
|
354
|
+
inputCostPerMillion: 1.75,
|
|
355
|
+
outputCostPerMillion: 14.0,
|
|
356
|
+
cacheWriteCostPerMillion: 0,
|
|
357
|
+
cacheReadCostPerMillion: 0.175,
|
|
358
|
+
},
|
|
359
|
+
'gpt-5.1-codex-mini': {
|
|
360
|
+
inputCostPerMillion: 0.25,
|
|
361
|
+
outputCostPerMillion: 2.0,
|
|
362
|
+
cacheWriteCostPerMillion: 0,
|
|
363
|
+
cacheReadCostPerMillion: 0.025,
|
|
364
|
+
},
|
|
365
|
+
'gpt-5-pro': {
|
|
366
|
+
inputCostPerMillion: 15.0,
|
|
367
|
+
outputCostPerMillion: 120.0,
|
|
368
|
+
cacheWriteCostPerMillion: 0,
|
|
369
|
+
cacheReadCostPerMillion: 0,
|
|
370
|
+
},
|
|
371
|
+
'gpt-5-mini': {
|
|
372
|
+
inputCostPerMillion: 0.25,
|
|
373
|
+
outputCostPerMillion: 2.0,
|
|
374
|
+
cacheWriteCostPerMillion: 0,
|
|
375
|
+
cacheReadCostPerMillion: 0.025,
|
|
376
|
+
},
|
|
377
|
+
'gpt-5-nano': {
|
|
378
|
+
inputCostPerMillion: 0.05,
|
|
379
|
+
outputCostPerMillion: 0.4,
|
|
380
|
+
cacheWriteCostPerMillion: 0,
|
|
381
|
+
cacheReadCostPerMillion: 0.005,
|
|
382
|
+
},
|
|
383
|
+
'gpt-5': {
|
|
384
|
+
inputCostPerMillion: 1.25,
|
|
385
|
+
outputCostPerMillion: 10.0,
|
|
386
|
+
cacheWriteCostPerMillion: 0,
|
|
387
|
+
cacheReadCostPerMillion: 0.125,
|
|
388
|
+
},
|
|
389
|
+
// ── OpenAI: o-series (reasoning models) ──
|
|
390
|
+
// The `-pro` and deep-research tiers cost an order of magnitude more than the
|
|
391
|
+
// base model whose key they would otherwise match.
|
|
392
|
+
'o3-deep-research': {
|
|
393
|
+
inputCostPerMillion: 10.0,
|
|
394
|
+
outputCostPerMillion: 40.0,
|
|
395
|
+
cacheWriteCostPerMillion: 0,
|
|
396
|
+
cacheReadCostPerMillion: 2.5,
|
|
397
|
+
},
|
|
398
|
+
'o3-mini': {
|
|
399
|
+
inputCostPerMillion: 1.1,
|
|
400
|
+
outputCostPerMillion: 4.4,
|
|
401
|
+
cacheWriteCostPerMillion: 0,
|
|
402
|
+
cacheReadCostPerMillion: 0.55,
|
|
403
|
+
},
|
|
404
|
+
'o3-pro': {
|
|
405
|
+
inputCostPerMillion: 20.0,
|
|
406
|
+
outputCostPerMillion: 80.0,
|
|
407
|
+
cacheWriteCostPerMillion: 0,
|
|
408
|
+
cacheReadCostPerMillion: 0,
|
|
409
|
+
},
|
|
410
|
+
o3: {
|
|
411
|
+
inputCostPerMillion: 2.0,
|
|
412
|
+
outputCostPerMillion: 8.0,
|
|
413
|
+
cacheWriteCostPerMillion: 0,
|
|
414
|
+
cacheReadCostPerMillion: 0.5,
|
|
415
|
+
},
|
|
416
|
+
// OpenAI cut o1-mini to $1.10/$4.40 after launch; $3/$12 was the launch rate.
|
|
417
|
+
// No bare catalog key settles it — Azure's $1.21/$4.84 is its usual 1.1x
|
|
418
|
+
// uplift on exactly this number, and Replicate's OpenAI passthrough agrees.
|
|
419
|
+
'o1-mini': {
|
|
420
|
+
inputCostPerMillion: 1.1,
|
|
421
|
+
outputCostPerMillion: 4.4,
|
|
422
|
+
cacheWriteCostPerMillion: 0,
|
|
423
|
+
cacheReadCostPerMillion: 0.55,
|
|
424
|
+
},
|
|
425
|
+
'o1-pro': {
|
|
426
|
+
inputCostPerMillion: 150.0,
|
|
427
|
+
outputCostPerMillion: 600.0,
|
|
428
|
+
cacheWriteCostPerMillion: 0,
|
|
429
|
+
cacheReadCostPerMillion: 0,
|
|
430
|
+
},
|
|
431
|
+
o1: {
|
|
432
|
+
inputCostPerMillion: 15.0,
|
|
433
|
+
outputCostPerMillion: 60.0,
|
|
434
|
+
cacheWriteCostPerMillion: 0,
|
|
435
|
+
cacheReadCostPerMillion: 7.5,
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
/** Static-table keys sorted longest-first so longest-prefix wins. */
|
|
439
|
+
const STATIC_SORTED_KEYS = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
|
|
440
|
+
// ── Override Table (populated at runtime by pricingCatalog) ──
|
|
441
|
+
let overrideTable = {};
|
|
442
|
+
let overrideSortedKeys = [];
|
|
443
|
+
let importedTable = {};
|
|
444
|
+
/**
|
|
445
|
+
* Internal: replace the runtime override map. Called by `pricingCatalog.ts`
|
|
446
|
+
* after a successful LiteLLM hydration. Not part of the stable public API,
|
|
447
|
+
* but exported so the Node-only catalog module can wire itself in without
|
|
448
|
+
* creating a circular import.
|
|
449
|
+
*/
|
|
450
|
+
export function _setPricingOverrides(overrides) {
|
|
451
|
+
overrideTable = Object.fromEntries(Object.entries(overrides).map(([key, value]) => [key.trim().toLowerCase(), { ...value }]));
|
|
452
|
+
overrideSortedKeys = Object.keys(overrideTable).sort((a, b) => b.length - a.length);
|
|
453
|
+
}
|
|
454
|
+
/** Internal: snapshot current overrides (test + diagnostics). */
|
|
455
|
+
export function _getPricingOverrides() {
|
|
456
|
+
return { ...overrideTable };
|
|
457
|
+
}
|
|
458
|
+
/** Internal: clear overrides (used by tests and when disabling hydration). */
|
|
459
|
+
export function _clearPricingOverrides() {
|
|
460
|
+
overrideTable = {};
|
|
461
|
+
overrideSortedKeys = [];
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Internal: hydrate exact pricing resolutions exported by another realm.
|
|
465
|
+
* Merges so successive partial imports accumulate (aliases already merge);
|
|
466
|
+
* use `_clearImportedResolvedPricing` to reset.
|
|
467
|
+
*/
|
|
468
|
+
export function _setImportedResolvedPricing(values) {
|
|
469
|
+
importedTable = { ...importedTable, ...values };
|
|
470
|
+
}
|
|
471
|
+
/** Internal test hook. */
|
|
472
|
+
export function _clearImportedResolvedPricing() {
|
|
473
|
+
importedTable = {};
|
|
474
|
+
}
|
|
475
|
+
/** Internal: list every ID known to the pricing resolver. */
|
|
476
|
+
export function _getModelPricingIds() {
|
|
477
|
+
return Array.from(new Set([
|
|
478
|
+
...Object.keys(PRICING_TABLE),
|
|
479
|
+
...Object.keys(overrideTable),
|
|
480
|
+
...Object.keys(importedTable),
|
|
481
|
+
]));
|
|
482
|
+
}
|
|
483
|
+
// ── Model ID Parsing ──
|
|
484
|
+
// Anthropic writes the minor version with a hyphen in modern IDs
|
|
485
|
+
// (`claude-sonnet-4-6-20260321`) and a dot in older ones
|
|
486
|
+
// (`claude-sonnet-4.5-20241022`). Accept both, then normalize to a dot.
|
|
487
|
+
// The `{1,2}` bound plus the trailing `(?![0-9])` keep the 8-digit release
|
|
488
|
+
// date from being mistaken for a minor version in `claude-opus-4-20250514`.
|
|
489
|
+
const CLAUDE_RE = /^claude-(haiku|sonnet|opus|fable)-([0-9]+(?:[-.][0-9]{1,2})?)(?![0-9])/i;
|
|
490
|
+
const LEGACY_CLAUDE_RE = /^claude-([0-9]+(?:[-.][0-9]+)?)-(haiku|sonnet|opus)(?:-|$)/i;
|
|
491
|
+
const GPT_RE = /^gpt-([0-9][0-9.A-Za-z-]*)/i;
|
|
492
|
+
const O_SERIES_RE = /^o([0-9]+)(-mini|-pro)?/i;
|
|
493
|
+
const GEMINI_RE = /^gemini-([0-9][0-9.A-Za-z-]*)/i;
|
|
494
|
+
/**
|
|
495
|
+
* Parses a model ID into {provider, family, version}.
|
|
496
|
+
*
|
|
497
|
+
* Input is trimmed and lowercased before matching, so padded or mixed-case
|
|
498
|
+
* IDs (e.g. " Claude-Opus-4-8 ") parse without caller-side normalization.
|
|
499
|
+
*
|
|
500
|
+
* Recognizes Anthropic (Claude), OpenAI (GPT + o-series), and Google (Gemini).
|
|
501
|
+
* Returns null for anything else — callers should treat that as "unknown model".
|
|
502
|
+
*/
|
|
503
|
+
export function parseModelId(modelId) {
|
|
504
|
+
if (!modelId)
|
|
505
|
+
return null;
|
|
506
|
+
const normalized = resolveModelAlias(modelId.replace(/\[1m\]/gi, ''));
|
|
507
|
+
const claude = normalized.match(CLAUDE_RE);
|
|
508
|
+
if (claude) {
|
|
509
|
+
return {
|
|
510
|
+
provider: 'anthropic',
|
|
511
|
+
family: claude[1].toLowerCase(),
|
|
512
|
+
version: claude[2].replace('-', '.'),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const legacyClaude = normalized.match(LEGACY_CLAUDE_RE);
|
|
516
|
+
if (legacyClaude) {
|
|
517
|
+
return {
|
|
518
|
+
provider: 'anthropic',
|
|
519
|
+
family: legacyClaude[2].toLowerCase(),
|
|
520
|
+
version: legacyClaude[1].replace('-', '.'),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const gpt = normalized.match(GPT_RE);
|
|
524
|
+
if (gpt) {
|
|
525
|
+
return { provider: 'openai', family: 'gpt', version: gpt[1] };
|
|
526
|
+
}
|
|
527
|
+
const oSeries = normalized.match(O_SERIES_RE);
|
|
528
|
+
if (oSeries) {
|
|
529
|
+
const suffix = oSeries[2] ? oSeries[2] : '';
|
|
530
|
+
return { provider: 'openai', family: 'o', version: `${oSeries[1]}${suffix}` };
|
|
531
|
+
}
|
|
532
|
+
const gemini = normalized.match(GEMINI_RE);
|
|
533
|
+
if (gemini) {
|
|
534
|
+
return { provider: 'google', family: 'gemini', version: gemini[1] };
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
// ── Pricing Lookup ──
|
|
539
|
+
/** Find the longest shorter key in `keys` that is a prefix of `modelId`, or null. */
|
|
540
|
+
function findLongestPrefix(keys, modelId) {
|
|
541
|
+
for (const key of keys) {
|
|
542
|
+
if (modelId !== key && modelId.startsWith(key))
|
|
543
|
+
return key;
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
/** Resolve pricing and disclose whether the result was inherited by prefix. */
|
|
548
|
+
export function resolveModelPricing(modelId) {
|
|
549
|
+
const stripped = modelId.replace(/\[1m\]/gi, '');
|
|
550
|
+
const rawId = stripped.trim().toLowerCase();
|
|
551
|
+
const canonicalModelId = resolveModelAlias(stripped);
|
|
552
|
+
// The raw id is tried before its alias target at every step: pricing
|
|
553
|
+
// recorded for the alias id itself is more specific than the canonical
|
|
554
|
+
// fallback, so registering an alias never orphans it.
|
|
555
|
+
const candidateIds = rawId === canonicalModelId ? [canonicalModelId] : [rawId, canonicalModelId];
|
|
556
|
+
const imported = importedTable[rawId] ?? importedTable[canonicalModelId];
|
|
557
|
+
if (imported)
|
|
558
|
+
return { ...imported, modelId, canonicalModelId };
|
|
559
|
+
for (const id of candidateIds) {
|
|
560
|
+
const exactOverride = overrideTable[id];
|
|
561
|
+
if (exactOverride) {
|
|
562
|
+
return {
|
|
563
|
+
modelId,
|
|
564
|
+
canonicalModelId,
|
|
565
|
+
pricing: exactOverride,
|
|
566
|
+
provenance: {
|
|
567
|
+
source: 'catalog',
|
|
568
|
+
match: 'exact',
|
|
569
|
+
matchedModelId: id,
|
|
570
|
+
inheritedByPrefix: false,
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
for (const id of candidateIds) {
|
|
576
|
+
const exactStatic = PRICING_TABLE[id];
|
|
577
|
+
if (exactStatic) {
|
|
578
|
+
return {
|
|
579
|
+
modelId,
|
|
580
|
+
canonicalModelId,
|
|
581
|
+
pricing: exactStatic,
|
|
582
|
+
provenance: {
|
|
583
|
+
source: 'static',
|
|
584
|
+
match: 'exact',
|
|
585
|
+
matchedModelId: id,
|
|
586
|
+
inheritedByPrefix: false,
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const id of candidateIds) {
|
|
592
|
+
const overridePrefix = findLongestPrefix(overrideSortedKeys, id);
|
|
593
|
+
if (overridePrefix) {
|
|
594
|
+
return {
|
|
595
|
+
modelId,
|
|
596
|
+
canonicalModelId,
|
|
597
|
+
pricing: overrideTable[overridePrefix],
|
|
598
|
+
provenance: {
|
|
599
|
+
source: 'catalog',
|
|
600
|
+
match: 'prefix',
|
|
601
|
+
matchedModelId: overridePrefix,
|
|
602
|
+
inheritedByPrefix: true,
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const id of candidateIds) {
|
|
608
|
+
const staticPrefix = findLongestPrefix(STATIC_SORTED_KEYS, id);
|
|
609
|
+
if (staticPrefix) {
|
|
610
|
+
return {
|
|
611
|
+
modelId,
|
|
612
|
+
canonicalModelId,
|
|
613
|
+
pricing: PRICING_TABLE[staticPrefix],
|
|
614
|
+
provenance: {
|
|
615
|
+
source: 'static',
|
|
616
|
+
match: 'prefix',
|
|
617
|
+
matchedModelId: staticPrefix,
|
|
618
|
+
inheritedByPrefix: true,
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
modelId,
|
|
625
|
+
canonicalModelId,
|
|
626
|
+
pricing: null,
|
|
627
|
+
provenance: { source: 'none', match: 'none', inheritedByPrefix: false },
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Gets pricing for a model ID.
|
|
632
|
+
*
|
|
633
|
+
* Lookup order:
|
|
634
|
+
* 1. Runtime override map (from LiteLLM catalog hydration).
|
|
635
|
+
* 2. Static PRICING_TABLE.
|
|
636
|
+
* 3. `null` — unknown model. Callers MUST handle this.
|
|
637
|
+
*
|
|
638
|
+
* The ID is first looked up verbatim (minus the "[1m]" suffix) — override
|
|
639
|
+
* keys from the LiteLLM catalog are stored as published and may be
|
|
640
|
+
* mixed-case — then retried trimmed/lowercased so padded or mixed-case IDs
|
|
641
|
+
* resolve against the lowercase tables.
|
|
642
|
+
*
|
|
643
|
+
* @returns Pricing for the model, or null if unknown. No silent fallback.
|
|
644
|
+
*/
|
|
645
|
+
export function getModelPricing(modelId) {
|
|
646
|
+
if (!modelId)
|
|
647
|
+
return null;
|
|
648
|
+
return resolveModelPricing(modelId).pricing;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Returns comprehensive metadata for a model ID.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* getModelInfo('claude-opus-4.5-20250514')
|
|
655
|
+
* // => { provider: 'anthropic', family: 'opus', version: '4.5',
|
|
656
|
+
* // contextWindow: 200000, pricing: { ... } }
|
|
657
|
+
*
|
|
658
|
+
* getModelInfo('gpt-4o')
|
|
659
|
+
* // => { provider: 'openai', family: 'gpt', version: '4o',
|
|
660
|
+
* // contextWindow: 128000, pricing: { ... } }
|
|
661
|
+
*/
|
|
662
|
+
export function getModelInfo(modelId) {
|
|
663
|
+
const parsed = parseModelId(modelId);
|
|
664
|
+
return {
|
|
665
|
+
provider: parsed?.provider ?? null,
|
|
666
|
+
family: parsed?.family ?? null,
|
|
667
|
+
version: parsed?.version ?? null,
|
|
668
|
+
contextWindow: getModelContextWindowSize(modelId),
|
|
669
|
+
pricing: getModelPricing(modelId),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
// ── Cost Calculation ──
|
|
673
|
+
/**
|
|
674
|
+
* Calculates cost from token usage and an explicit pricing object.
|
|
675
|
+
*
|
|
676
|
+
* Legacy behavior treats reasoning as disjoint and adds it at the output rate.
|
|
677
|
+
* @deprecated Use calculateNormalizedUsageCost. This legacy function always
|
|
678
|
+
* adds reasoning to output and cannot represent provider inclusion semantics.
|
|
679
|
+
*/
|
|
680
|
+
export function calculateCostWithPricing(usage, pricing) {
|
|
681
|
+
const reasoning = usage.reasoningTokens ?? 0;
|
|
682
|
+
return ((usage.inputTokens / 1_000_000) * pricing.inputCostPerMillion +
|
|
683
|
+
(usage.outputTokens / 1_000_000) * pricing.outputCostPerMillion +
|
|
684
|
+
(reasoning / 1_000_000) * pricing.outputCostPerMillion +
|
|
685
|
+
(usage.cacheWriteTokens / 1_000_000) * pricing.cacheWriteCostPerMillion +
|
|
686
|
+
(usage.cacheReadTokens / 1_000_000) * pricing.cacheReadCostPerMillion);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Calculates USD cost from token usage and a model ID.
|
|
690
|
+
*
|
|
691
|
+
* Returns `null` for unknown models. Callers should render `—` in that case,
|
|
692
|
+
* not $0 — so users don't confuse "missing pricing" with "free".
|
|
693
|
+
* @deprecated Use calculateNormalizedUsageCost.
|
|
694
|
+
*/
|
|
695
|
+
export function calculateCost(usage, modelId) {
|
|
696
|
+
const pricing = getModelPricing(modelId);
|
|
697
|
+
if (!pricing)
|
|
698
|
+
return null;
|
|
699
|
+
return calculateCostWithPricing(usage, pricing);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Calculates cost while preserving whether the value was provider-reported,
|
|
703
|
+
* locally estimated from pricing, or unavailable because the model is unpriced.
|
|
704
|
+
* @deprecated Use calculateNormalizedUsageCost.
|
|
705
|
+
*/
|
|
706
|
+
export function calculateCostWithProvenance(input) {
|
|
707
|
+
if (typeof input.reportedCostUsd === 'number' && Number.isFinite(input.reportedCostUsd)) {
|
|
708
|
+
return { costUsd: input.reportedCostUsd, source: 'reported' };
|
|
709
|
+
}
|
|
710
|
+
const estimated = calculateCost(input.usage, input.modelId);
|
|
711
|
+
if (estimated === null)
|
|
712
|
+
return { source: 'unpriced' };
|
|
713
|
+
return { costUsd: estimated, source: 'estimated' };
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Merge two cost sources for rollups. The least certain source wins so a total
|
|
717
|
+
* containing any unpriced or estimated component does not look fully reported.
|
|
718
|
+
*/
|
|
719
|
+
export function mergeCostSources(a, b) {
|
|
720
|
+
const rank = {
|
|
721
|
+
reported: 0,
|
|
722
|
+
estimated: 1,
|
|
723
|
+
unpriced: 2,
|
|
724
|
+
};
|
|
725
|
+
return rank[a] >= rank[b] ? a : b;
|
|
726
|
+
}
|
|
727
|
+
// ── Display ──
|
|
728
|
+
function normalizeModelId(modelId) {
|
|
729
|
+
return modelId
|
|
730
|
+
.trim()
|
|
731
|
+
.toLowerCase()
|
|
732
|
+
.replace(/^[a-z]+\//, '')
|
|
733
|
+
.replace(/-\d{8}$/, '')
|
|
734
|
+
.replace(/-latest$/, '')
|
|
735
|
+
.replace(/\[1m\]/gi, '');
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Short display label for model IDs. Keeps historical Claude family labels
|
|
739
|
+
* compact while normalizing common OpenAI labels.
|
|
740
|
+
*/
|
|
741
|
+
export function shortModelName(modelId) {
|
|
742
|
+
const normalized = normalizeModelId(modelId);
|
|
743
|
+
const parsed = parseModelId(normalized);
|
|
744
|
+
if (normalized.includes('codex'))
|
|
745
|
+
return 'Codex';
|
|
746
|
+
if (parsed?.provider === 'anthropic') {
|
|
747
|
+
const family = parsed.family.toLowerCase();
|
|
748
|
+
return family.charAt(0).toUpperCase() + family.slice(1);
|
|
749
|
+
}
|
|
750
|
+
if (parsed?.provider === 'openai') {
|
|
751
|
+
if (parsed.family === 'o')
|
|
752
|
+
return `o${parsed.version}`;
|
|
753
|
+
if (normalized.startsWith('gpt-4o-mini'))
|
|
754
|
+
return 'GPT-4o mini';
|
|
755
|
+
if (normalized.startsWith('gpt-4o'))
|
|
756
|
+
return 'GPT-4o';
|
|
757
|
+
if (parsed.family === 'gpt')
|
|
758
|
+
return `GPT-${parsed.version}`;
|
|
759
|
+
}
|
|
760
|
+
return modelId;
|
|
761
|
+
}
|
|
762
|
+
const CLAUDE_FAMILY_RANK = {
|
|
763
|
+
fable: 0,
|
|
764
|
+
opus: 1,
|
|
765
|
+
sonnet: 2,
|
|
766
|
+
haiku: 3,
|
|
767
|
+
};
|
|
768
|
+
function versionRank(version) {
|
|
769
|
+
if (!version)
|
|
770
|
+
return Number.MAX_SAFE_INTEGER;
|
|
771
|
+
const numeric = Number(version.replace('-', '.').match(/[0-9]+(?:\.[0-9]+)?/)?.[0]);
|
|
772
|
+
if (!Number.isFinite(numeric))
|
|
773
|
+
return Number.MAX_SAFE_INTEGER;
|
|
774
|
+
return -numeric;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Returns stable display metadata and a rank suitable for provider model menus.
|
|
778
|
+
*/
|
|
779
|
+
export function getModelDisplayInfo(modelId) {
|
|
780
|
+
const parsed = parseModelId(modelId);
|
|
781
|
+
const normalized = normalizeModelId(modelId);
|
|
782
|
+
let rank = 1_000;
|
|
783
|
+
if (parsed?.provider === 'anthropic') {
|
|
784
|
+
rank = (CLAUDE_FAMILY_RANK[parsed.family] ?? 9) * 100 + versionRank(parsed.version);
|
|
785
|
+
}
|
|
786
|
+
else if (normalized.includes('codex')) {
|
|
787
|
+
rank = 200;
|
|
788
|
+
}
|
|
789
|
+
else if (parsed?.provider === 'openai') {
|
|
790
|
+
rank =
|
|
791
|
+
parsed.family === 'gpt'
|
|
792
|
+
? 300 + versionRank(parsed.version)
|
|
793
|
+
: 400 + versionRank(parsed.version);
|
|
794
|
+
}
|
|
795
|
+
else if (parsed?.provider === 'google') {
|
|
796
|
+
rank = 500 + versionRank(parsed.version);
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
modelId,
|
|
800
|
+
provider: parsed?.provider ?? null,
|
|
801
|
+
family: parsed?.family ?? null,
|
|
802
|
+
version: parsed?.version ?? null,
|
|
803
|
+
shortName: shortModelName(modelId),
|
|
804
|
+
rank,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
/** Compare two model IDs using shared provider/family ranking rules. */
|
|
808
|
+
export function compareModelIds(a, b) {
|
|
809
|
+
const left = getModelDisplayInfo(a);
|
|
810
|
+
const right = getModelDisplayInfo(b);
|
|
811
|
+
if (left.rank !== right.rank)
|
|
812
|
+
return left.rank - right.rank;
|
|
813
|
+
return a.localeCompare(b);
|
|
814
|
+
}
|
|
815
|
+
/** Return a sorted copy of model IDs using shared provider/family ranking rules. */
|
|
816
|
+
export function sortModelIds(modelIds) {
|
|
817
|
+
return [...modelIds].sort(compareModelIds);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Formats a USD cost as a currency string.
|
|
821
|
+
*
|
|
822
|
+
* - `null` / `undefined` → `"—"` (honest "pricing unavailable").
|
|
823
|
+
* - `< $0.01` → 4 decimals, so sub-cent costs are still visible.
|
|
824
|
+
* - otherwise → 2 decimals.
|
|
825
|
+
*/
|
|
826
|
+
export function formatCost(cost) {
|
|
827
|
+
if (cost === null || cost === undefined)
|
|
828
|
+
return '—';
|
|
829
|
+
if (cost < 0.01)
|
|
830
|
+
return `$${cost.toFixed(4)}`;
|
|
831
|
+
return `$${cost.toFixed(2)}`;
|
|
832
|
+
}
|