From a3380b2453006fc55ff96a18414f31e5f323cb70 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Sun, 26 Jul 2026 08:18:23 -0700 Subject: [PATCH] =?UTF-8?q?refactor(pricing):=20fan-out=20batch=202=20?= =?UTF-8?q?=E2=80=94=20zed,=20gemini,=20kimi,=20pi/omp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/providers/gemini.ts | 8 +------- src/providers/kimi.ts | 12 ++---------- src/providers/pi.ts | 4 +--- src/providers/zed.ts | 3 +-- tests/providers/kimi.test.ts | 3 ++- tests/providers/omp.test.ts | 9 +++++---- tests/providers/pi.test.ts | 29 +++++++++++++++-------------- tests/providers/zed.test.ts | 3 ++- 8 files changed, 29 insertions(+), 42 deletions(-) diff --git a/src/providers/gemini.ts b/src/providers/gemini.ts index e0333f0..7eb805a 100644 --- a/src/providers/gemini.ts +++ b/src/providers/gemini.ts @@ -3,7 +3,6 @@ import { join } from 'path' import { homedir } from 'os' import { readSessionFile } from '../fs-utils.js' -import { calculateCost } from '../models.js' import { extractBashCommands } from '../bash-utils.js' import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js' @@ -119,11 +118,6 @@ function parseSession(data: GeminiSession, seenKeys: Set): ParsedProvide seenKeys.add(dedupKey) - // Gemini bills thoughts at the output token rate; calculateCost does not - // accept a reasoning parameter, so fold thoughts into the output count for - // pricing while keeping outputTokens / reasoningTokens reported separately. - const costUSD = calculateCost(msg.model, freshInput, totalOutput + totalThoughts, 0, totalCached, 0) - results.push({ provider: 'gemini', model: msg.model, @@ -134,7 +128,7 @@ function parseSession(data: GeminiSession, seenKeys: Set): ParsedProvide cachedInputTokens: totalCached, reasoningTokens: totalThoughts, webSearchRequests: 0, - costUSD, + costBasis: 'estimated', tools: [...new Set(tools)], bashCommands: [...new Set(bashCommands)], timestamp: tsDate.toISOString(), diff --git a/src/providers/kimi.ts b/src/providers/kimi.ts index 75242cc..3a8350d 100644 --- a/src/providers/kimi.ts +++ b/src/providers/kimi.ts @@ -5,7 +5,7 @@ import { homedir } from 'os' import { extractBashCommands } from '../bash-utils.js' import { readSessionLines } from '../fs-utils.js' -import { calculateCost, getShortModelName } from '../models.js' +import { getShortModelName } from '../models.js' import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js' type JsonObject = Record @@ -297,14 +297,6 @@ function createParser(source: SessionSource, shareDir: string, seenKeys: Set): SessionPars return typeof cmd === 'string' ? extractBashCommands(cmd) : [] }) - const costUSD = calculateCost(model, input, output, cacheWrite, cacheRead, 0) const timestamp = entry.timestamp ?? '' yield { @@ -236,7 +234,7 @@ function createParser(source: SessionSource, seenKeys: Set): SessionPars cachedInputTokens: cacheRead, reasoningTokens: 0, webSearchRequests: 0, - costUSD, + costBasis: 'estimated', tools, bashCommands, skills, diff --git a/src/providers/zed.ts b/src/providers/zed.ts index 7164149..5951c0e 100644 --- a/src/providers/zed.ts +++ b/src/providers/zed.ts @@ -3,7 +3,6 @@ import { join } from 'path' import { homedir } from 'os' import zlib from 'zlib' -import { calculateCost } from '../models.js' import { getSqliteLoadError, isSqliteAvailable, openDatabase, type SqliteDatabase } from '../sqlite.js' import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js' @@ -88,7 +87,7 @@ function buildCall(opts: { cachedInputTokens: cacheRead, reasoningTokens: 0, webSearchRequests: 0, - costUSD: calculateCost(opts.model, input, output, cacheWrite, cacheRead, 0), + costBasis: 'estimated', tools: [], bashCommands: [], timestamp: opts.timestamp, diff --git a/tests/providers/kimi.test.ts b/tests/providers/kimi.test.ts index 486a03e..3c4e2b9 100644 --- a/tests/providers/kimi.test.ts +++ b/tests/providers/kimi.test.ts @@ -5,6 +5,7 @@ import { join } from 'path' import { tmpdir } from 'os' import { createKimiProvider } from '../../src/providers/kimi.js' +import { priceProviderCall } from '../../src/pricing-pass.js' import type { ParsedProviderCall } from '../../src/providers/types.js' let tmpDir: string @@ -44,7 +45,7 @@ async function writeSession(workDir: string, sessionId: string, lines: string[]) async function collect(provider: ReturnType, path: string, seen = new Set()): Promise { const parser = provider.createSessionParser({ path, project: 'app', provider: 'kimi' }, seen) const calls: ParsedProviderCall[] = [] - for await (const call of parser.parse()) calls.push(call) + for await (const call of parser.parse()) calls.push(priceProviderCall(call)) return calls } diff --git a/tests/providers/omp.test.ts b/tests/providers/omp.test.ts index 6a25c27..5ba76ab 100644 --- a/tests/providers/omp.test.ts +++ b/tests/providers/omp.test.ts @@ -4,6 +4,7 @@ import { join } from 'path' import { tmpdir } from 'os' import { createOmpProvider } from '../../src/providers/pi.js' +import { priceProviderCall } from '../../src/pricing-pass.js' import type { ParsedProviderCall } from '../../src/providers/types.js' let tmpDir: string @@ -149,7 +150,7 @@ describe('omp provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'omp' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(1) @@ -179,7 +180,7 @@ describe('omp provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'omp' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } // cost must be calculated by codeburn, not taken from usage.cost (which is zeroed in fixture) @@ -199,7 +200,7 @@ describe('omp provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'omp' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.tools).toEqual(['Read', 'Edit', 'Bash']) @@ -217,7 +218,7 @@ describe('omp provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'omp' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(0) diff --git a/tests/providers/pi.test.ts b/tests/providers/pi.test.ts index 398d509..f2efc73 100644 --- a/tests/providers/pi.test.ts +++ b/tests/providers/pi.test.ts @@ -4,6 +4,7 @@ import { join } from 'path' import { tmpdir } from 'os' import { createPiProvider } from '../../src/providers/pi.js' +import { priceProviderCall } from '../../src/pricing-pass.js' import type { ParsedProviderCall } from '../../src/providers/types.js' import { classifyTurn } from '../../src/classifier.js' import type { ParsedApiCall, ParsedTurn } from '../../src/types.js' @@ -208,7 +209,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(1) @@ -249,7 +250,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } // The assistant turn is still parsed, and the string user content is paired. @@ -275,7 +276,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.tools).toEqual(['Read', 'Edit', 'Bash']) @@ -296,7 +297,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.bashCommands).toEqual(['git', 'bun']) @@ -319,7 +320,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } // The SKILL.md read is surfaced as the Skill tool (not Read); the plain @@ -341,7 +342,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.skills).toEqual(['commit-workflow']) @@ -361,7 +362,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.skills).toEqual(['deep-research']) @@ -381,7 +382,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls[0]!.skills).toEqual([]) @@ -401,7 +402,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } // End to end: the parsed skill load must reach `subCategory`, or the @@ -422,7 +423,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(0) @@ -441,12 +442,12 @@ describe('pi provider - JSONL parsing', () => { const firstRun: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, seenKeys).parse()) { - firstRun.push(call) + firstRun.push(priceProviderCall(call)) } const secondRun: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, seenKeys).parse()) { - secondRun.push(call) + secondRun.push(priceProviderCall(call)) } expect(firstRun).toHaveLength(1) @@ -467,7 +468,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: filePath, project: 'myproject', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(2) @@ -482,7 +483,7 @@ describe('pi provider - JSONL parsing', () => { const source = { path: '/nonexistent/session.jsonl', project: 'test', provider: 'pi' } const calls: ParsedProviderCall[] = [] for await (const call of provider.createSessionParser(source, new Set()).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } expect(calls).toHaveLength(0) }) diff --git a/tests/providers/zed.test.ts b/tests/providers/zed.test.ts index 4762cd7..90ed399 100644 --- a/tests/providers/zed.test.ts +++ b/tests/providers/zed.test.ts @@ -7,6 +7,7 @@ import zlib from 'zlib' import { createZedProvider } from '../../src/providers/zed.js' import { isSqliteAvailable } from '../../src/sqlite.js' +import { priceProviderCall } from '../../src/pricing-pass.js' import type { ParsedProviderCall } from '../../src/providers/types.js' const requireForTest = createRequire(import.meta.url) @@ -76,7 +77,7 @@ async function collectCalls(dbPath: string, seenKeys = new Set()): Promi const calls: ParsedProviderCall[] = [] for (const source of sources) { for await (const call of provider.createSessionParser(source, seenKeys).parse()) { - calls.push(call) + calls.push(priceProviderCall(call)) } } return calls