mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-20 14:04:25 +00:00
Add unpriced filter to models report (#985)
This commit is contained in:
parent
cbb09d0170
commit
7e421b14bb
2 changed files with 75 additions and 3 deletions
13
src/main.ts
13
src/main.ts
|
|
@ -2075,6 +2075,7 @@ program
|
|||
.option('--by-agent', 'One row per (provider, model, agent) instead of one row per (provider, model). Claude subagent transcripts only; other providers and main sessions bucket under "main"')
|
||||
.option('--top <n>', 'Show only the top N rows', (v: string) => parseInt(v, 10))
|
||||
.option('--min-cost <usd>', 'Hide rows below this cost threshold', (v: string) => parseFloat(v))
|
||||
.option('--unpriced', 'Show only models with usage that currently price at $0')
|
||||
.option('--no-totals', 'Suppress the footer totals row')
|
||||
.option('--format <format>', 'Output format: table, markdown, json, csv', 'table')
|
||||
.action(async (opts) => {
|
||||
|
|
@ -2099,13 +2100,21 @@ program
|
|||
}
|
||||
|
||||
const projects = await parseAllSessions(range, opts.provider)
|
||||
const rows = await aggregateModels(projects, {
|
||||
let rows = await aggregateModels(projects, {
|
||||
byTask: !!opts.byTask,
|
||||
byAgent: !!opts.byAgent,
|
||||
taskFilter: opts.task,
|
||||
topN: typeof opts.top === 'number' && Number.isFinite(opts.top) ? opts.top : undefined,
|
||||
minCost: typeof opts.minCost === 'number' && Number.isFinite(opts.minCost) ? opts.minCost : 0.01,
|
||||
minCost: typeof opts.minCost === 'number' && Number.isFinite(opts.minCost) ? opts.minCost : (opts.unpriced ? 0 : 0.01),
|
||||
})
|
||||
if (opts.unpriced) {
|
||||
rows = rows.filter(row => findUnpricedModels([{
|
||||
model: row.model,
|
||||
calls: row.calls,
|
||||
cost: row.costUSD,
|
||||
tokens: row.totalTokens,
|
||||
}]).length > 0)
|
||||
}
|
||||
|
||||
const fmt = (opts.format ?? 'table').toLowerCase()
|
||||
if (rows.length === 0 && (fmt === 'table' || fmt === 'markdown')) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import chalk from 'chalk'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
|
||||
|
|
@ -713,6 +716,66 @@ describe('renderCsv', () => {
|
|||
})
|
||||
|
||||
describe('models CLI breakdown flags', () => {
|
||||
vi.setConfig({ testTimeout: 30_000 })
|
||||
|
||||
it('filters the models report to unpriced rows', async () => {
|
||||
const home = await mkdtemp(join(tmpdir(), 'codeburn-models-unpriced-'))
|
||||
try {
|
||||
const projectDir = join(home, '.claude', 'projects', 'models-unpriced')
|
||||
await mkdir(projectDir, { recursive: true })
|
||||
await writeFile(join(projectDir, 'session.jsonl'), [
|
||||
JSON.stringify({
|
||||
type: 'user',
|
||||
sessionId: 'models-unpriced-session',
|
||||
timestamp: '2026-05-09T00:00:00.000Z',
|
||||
cwd: '/tmp/models-unpriced',
|
||||
message: { role: 'user', content: 'Use one priced and one unpriced model.' },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'assistant',
|
||||
sessionId: 'models-unpriced-session',
|
||||
timestamp: '2026-05-09T00:01:00.000Z',
|
||||
cwd: '/tmp/models-unpriced',
|
||||
message: {
|
||||
id: 'priced',
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
model: 'claude-sonnet-4-6',
|
||||
content: [{ type: 'text', text: 'priced' }],
|
||||
usage: { input_tokens: 1000, output_tokens: 100, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 },
|
||||
},
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'assistant',
|
||||
sessionId: 'models-unpriced-session',
|
||||
timestamp: '2026-05-09T00:02:00.000Z',
|
||||
cwd: '/tmp/models-unpriced',
|
||||
message: {
|
||||
id: 'unpriced',
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
model: 'zz-unpriced-frontier-model',
|
||||
content: [{ type: 'text', text: 'unpriced' }],
|
||||
usage: { input_tokens: 2000, output_tokens: 200, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 },
|
||||
},
|
||||
}),
|
||||
].join('\n') + '\n')
|
||||
|
||||
const res = spawnSync(
|
||||
process.execPath,
|
||||
['--import', 'tsx', 'src/cli.ts', 'models', '--unpriced', '--from', '2026-05-09', '--to', '2026-05-09', '--provider', 'claude', '--format', 'json'],
|
||||
{ cwd: process.cwd(), env: { ...process.env, HOME: home, CLAUDE_CONFIG_DIR: join(home, '.claude'), CODEBURN_CACHE_DIR: join(home, '.cache', 'codeburn'), TZ: 'UTC' }, encoding: 'utf-8', timeout: 30_000 },
|
||||
)
|
||||
|
||||
expect(res.status, `stdout: ${res.stdout}\nstderr: ${res.stderr}`).toBe(0)
|
||||
const rows = JSON.parse(res.stdout) as Array<{ model: string; calls: number }>
|
||||
expect(rows.map(row => row.model)).toEqual(['zz-unpriced-frontier-model'])
|
||||
expect(rows[0]?.calls).toBe(1)
|
||||
} finally {
|
||||
await rm(home, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects --by-task and --by-agent together with a clear error and exit 1', () => {
|
||||
const res = spawnSync(
|
||||
process.execPath,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue