mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-21 05:23:19 +00:00
fix(app): pass --min-cost 0 so Models shows sub-cent and unpriced rows (#1474)
The CLI defaults minCost to $0.01 and the desktop bridge passed no override, so the Models table silently dropped every row under a cent. That also excluded every unpriced row, since a 0 >= 0.01 predicate can never hold, leaving #1443's dimming and add-alias affordances unreachable in the shipped app. codeburn:getModels now passes --min-cost 0, and the demo bridge mirrors it. --unpriced would have been wrong: it filters to only the unpriced rows rather than completing the set. On a frozen lifetime range the flag is exactly additive: 41 rows and 168,141 calls become 62 rows and 170,314 calls, +2,173 calls and +$0.007890, every original row unchanged. 20 of the 21 recovered rows are unpriced; one is a genuinely priced sub-cent row. The app agrees, 41 rows before and 62 after, with 20 dimmed rows and 20 add-alias buttons where there were none. Fixes #1465.
This commit is contained in:
parent
ac73c8dcd8
commit
f899e3d824
4 changed files with 13 additions and 6 deletions
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Fixed (desktop)
|
||||
- **The Models table shows every model that ran, including the ones under a cent.** The CLI's `models` command defaults `minCost` to $0.01, and the desktop bridge passed neither `--min-cost` nor `--unpriced`, so the table silently dropped every row below a cent — which by construction excluded every unpriced row too (a `0 >= 0.01` filter), leaving #1443's dimming and add-alias affordances unreachable in the shipped app. `codeburn:getModels` now passes `--min-cost 0` (and the demo bridge mirrors it), so sub-cent and unpriced rows arrive and render with their existing dim treatment; on a real lifetime corpus that recovers 10 rows and 2,160 calls the default filter hid (43 → 53 rows, verified in both themes). A row priced between $0.00 and $0.01 renders as "$0.00" without dimming — it is genuinely priced, just below the display floor. Fixes #1465.
|
||||
|
||||
### Added
|
||||
- **Hovering a dollar amount in the desktop shows the tokens behind it.** Hover or tab to any money figure that has token counts behind it — the Overview hero, the Yesterday, 7-day avg and Month to date figures, the daily chart bars and its Avg/day, Peak and Yesterday chips, the Sessions and Models rows and the two Compare tables — and a small popover lists input, output, cache read and cache write tokens plus the calls they came from. A figure with no token counts behind it shows nothing rather than a fabricated breakdown: projected month and the other estimates, percentages, savings, retry tax, plans and the by-project and by-PR rows are left exactly as they were. The amounts themselves are unchanged in size, weight and position, and the popover is drawn above the card so it never clips. The menubar payload's `periodTotals` now carries the same four counts, so the hero keeps its breakdown when its number comes from the machine-wide generation rather than the payload's own period.
|
||||
- **A model billed through a different door is its own row, on every surface.** Claude Code with `CLAUDE_CODE_USE_BEDROCK=1` and Hermes on its `bedrock` provider record Bedrock's id (`anthropic.claude-haiku-4-5-20251001-v1:0`, `openai.gpt-5.6-luna`) where a direct session records the vendor's; Hermes also records the door itself in `billing_provider`, which is how a Bedrock session is told apart from a direct one when the model id is the same. Those rows either surfaced as raw ids or, aliased to the friendly name, would have merged with the direct row — hiding which account the spend landed on, and so how much of the month was metered API usage on top of a subscription. Every report now keys model rows on one function, `modelRowKey`: the short name plus the door's label, so `Haiku 4.5`, `Haiku 4.5 (Bedrock)` and `Haiku 4.5 (Bedrock us)` (the cross-region profile, a dearer SKU) are three rows in `codeburn models`, the dashboard and the menubar payload's `topModels` alike, where before `models` and the menubar could disagree. The route rides on the call (`route`), and `codeburn models --format json` reports it per row. Pricing is untouched — it runs on the raw id. Daily cache moves to v33 (`day.models` keyed by the row key, so a column-sourced route survives into finalized days); Hermes sessions re-parse once. (#1449, #1450)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const ROUTES = {
|
|||
getOverview: (period, provider, r) => ['status', '--format', 'menubar-json', '--period', period, '--no-timeline', ...prov(provider), ...range(r)],
|
||||
getTimeline: (period, provider, r) => ['status', '--format', 'menubar-json', '--period', period, ...prov(provider), ...range(r)],
|
||||
getPlans: (period) => ['status', '--format', 'json', '--period', period],
|
||||
getModels: (period, provider, byTask, r) => ['models', '--format', 'json', '--period', period, ...prov(provider), ...(byTask ? ['--by-task'] : []), ...range(r)],
|
||||
getModels: (period, provider, byTask, r) => ['models', '--format', 'json', '--period', period, '--min-cost', '0', ...prov(provider), ...(byTask ? ['--by-task'] : []), ...range(r)],
|
||||
getSessions: (period, provider, r) => ['sessions', '--format', 'json', '--period', period, ...prov(provider), ...range(r)],
|
||||
getSessionsContributions: (period, provider, r) => ['sessions', '--format', 'json', '--contributions', '--period', period, ...prov(provider), ...range(r)],
|
||||
getCompareModels: (period, provider) => ['compare', '--format', 'json', '--period', period, ...prov(provider)],
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ const ARGV_CASES: Array<{ channel: string; args: unknown[]; argv: string[] }> =
|
|||
{ channel: 'codeburn:getOverview', args: ['30days', 'all'], argv: ['status', '--format', 'menubar-json', '--period', '30days', '--no-timeline'] },
|
||||
{ channel: 'codeburn:getPlans', args: ['week'], argv: ['status', '--format', 'json', '--period', 'week'] },
|
||||
{ channel: 'codeburn:getActReport', args: [], argv: ['act', 'report', '--json'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'claude', true], argv: ['models', '--format', 'json', '--period', 'week', '--provider', 'claude', '--by-task'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'all', false], argv: ['models', '--format', 'json', '--period', 'week'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'claude', true], argv: ['models', '--format', 'json', '--period', 'week', '--min-cost', '0', '--provider', 'claude', '--by-task'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'all', false], argv: ['models', '--format', 'json', '--period', 'week', '--min-cost', '0'] },
|
||||
{ channel: 'codeburn:getSessions', args: ['week', 'all'], argv: ['sessions', '--format', 'json', '--period', 'week'] },
|
||||
{ channel: 'codeburn:getSessions', args: ['30days', 'claude', { from: '2026-07-01', to: '2026-07-11' }], argv: ['sessions', '--format', 'json', '--period', '30days', '--provider', 'claude', '--from', '2026-07-01', '--to', '2026-07-11'] },
|
||||
{ channel: 'codeburn:getSessionsContributions', args: ['week', 'all'], argv: ['sessions', '--format', 'json', '--contributions', '--period', 'week'] },
|
||||
|
|
@ -132,7 +132,7 @@ const ARGV_CASES: Array<{ channel: string; args: unknown[]; argv: string[] }> =
|
|||
{ channel: 'codeburn:getOverview', args: ['30days', 'all', undefined, undefined, undefined, 'combined'], argv: ['status', '--format', 'menubar-json', '--period', '30days', '--no-timeline', '--scope', 'combined'] },
|
||||
{ channel: 'codeburn:getOverview', args: ['30days', 'claude', undefined, undefined, undefined, 'combined'], argv: ['status', '--format', 'menubar-json', '--period', '30days', '--no-timeline', '--scope', 'combined'] },
|
||||
{ channel: 'codeburn:getOverview', args: ['30days', 'claude', undefined, undefined, undefined, 'local'], argv: ['status', '--format', 'menubar-json', '--period', '30days', '--no-timeline', '--provider', 'claude'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'claude', true, { from: '2026-07-01', to: '2026-07-11' }], argv: ['models', '--format', 'json', '--period', 'week', '--provider', 'claude', '--by-task', '--from', '2026-07-01', '--to', '2026-07-11'] },
|
||||
{ channel: 'codeburn:getModels', args: ['week', 'claude', true, { from: '2026-07-01', to: '2026-07-11' }], argv: ['models', '--format', 'json', '--period', 'week', '--min-cost', '0', '--provider', 'claude', '--by-task', '--from', '2026-07-01', '--to', '2026-07-11'] },
|
||||
{ channel: 'codeburn:getYield', args: ['today', 'all', { from: '2026-07-01', to: '2026-07-11' }], argv: ['yield', '--format', 'json', '--period', 'today', '--from', '2026-07-01', '--to', '2026-07-11'] },
|
||||
{ channel: 'codeburn:getSpendFlow', args: ['month', 'all', { from: '2026-07-01', to: '2026-07-11' }], argv: ['spend', '--format', 'flow-json', '--period', 'month', '--from', '2026-07-01', '--to', '2026-07-11'] },
|
||||
{ channel: 'codeburn:getBranchSpend', args: ['month', 'openai'], argv: ['spend', '--format', 'branch-json', '--period', 'month', '--provider', 'openai'] },
|
||||
|
|
@ -231,7 +231,7 @@ describe('createBridgeHandlers (IPC wiring)', () => {
|
|||
const { spawnCli, spawnCliAction, calls } = fakeSpawn([])
|
||||
const handlers = createBridgeHandlers(withQuota({ spawnCli, spawnCliAction, resolveCodeburnPath: () => null }))
|
||||
await handlers['codeburn:getModels']!('week', 'claude', true)
|
||||
expect(calls[0]).toEqual(['models', '--format', 'json', '--period', 'week', '--provider', 'claude', '--by-task'])
|
||||
expect(calls[0]).toEqual(['models', '--format', 'json', '--period', 'week', '--min-cost', '0', '--provider', 'claude', '--by-task'])
|
||||
})
|
||||
|
||||
it('returns an error envelope carrying the CliError kind', async () => {
|
||||
|
|
|
|||
|
|
@ -571,7 +571,11 @@ export function createBridgeHandlers(deps: Deps = { spawnCli, spawnCliAction, re
|
|||
'codeburn:getPlans': run((period: string) => ['status', '--format', 'json', '--period', vPeriod(period)], 1),
|
||||
'codeburn:getActReport': run(() => ['act', 'report', '--json']),
|
||||
'codeburn:getModels': run((period: string, provider: string, byTask: boolean, range?: DateRange) => [
|
||||
'models', '--format', 'json', '--period', vPeriod(period),
|
||||
// The CLI defaults minCost to $0.01, which silently dropped every row
|
||||
// below a cent — including ALL unpriced rows, so the dimming and
|
||||
// add-alias affordances could never fire. Ask for the whole table;
|
||||
// the renderer already distinguishes unpriced rows (#1465).
|
||||
'models', '--format', 'json', '--period', vPeriod(period), '--min-cost', '0',
|
||||
...providerArgs(vProvider(provider)),
|
||||
...projectArgs(),
|
||||
...(byTask ? ['--by-task'] : []),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue