refactor(pricing): gap batch — cursor, kimicode; codex per investigation

This commit is contained in:
iamtoruk 2026-07-26 09:44:10 -07:00
parent 6552b10bd8
commit a30bfed80e
5 changed files with 20 additions and 16 deletions

View file

@ -540,6 +540,11 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
if (seenKeys.has(dedupKey)) { pendingTools = []; pendingToolSequence = []; pendingUserMessage = ''; pendingOutputChars = 0; pendingLocAdded = 0; pendingLocRemoved = 0; pendingEditFailed = 0; continue }
seenKeys.add(dedupKey)
// Phase-0 residual (issue #809): NOT converted to costBasis:'estimated'.
// codex-cache.ts persists the whole ParsedProviderCall (costUSD included);
// dropping costUSD here changes every newly-written cache entry without a
// CODEX_CACHE_VERSION bump, yielding a mixed-format v7 cache. Deferred to
// Phase 4 (codex decoder carve-out owns its result-cache state).
const costUSD = calculateCost(model, estInput, estOutput, 0, 0, 0)
results.push({
@ -651,6 +656,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
if (seenKeys.has(dedupKey)) continue
seenKeys.add(dedupKey)
// Phase-0 residual (issue #809): left on the in-decoder pricing path for
// the same reason as the estimate branch above — codex-cache.ts persists
// costUSD, so converting would change cached bytes without a version bump.
// Deferred to Phase 4.
const costUSD = calculateCost(
model,
uncachedInputTokens,

View file

@ -2,7 +2,6 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
import { calculateCost } from '../models.js'
import { extractBashCommands } from '../bash-utils.js'
import { readCachedResults, writeCachedResults } from '../cursor-cache.js'
import { isSqliteAvailable, isSqliteBusyError, getSqliteLoadError, openDatabase, blobToText, type SqliteDatabase } from '../sqlite.js'
@ -821,7 +820,6 @@ function parseBubbles(
// conversation's model seen on its assistant bubbles or agent stream.
const effectiveModel = row.model ?? scans.get(conversationId)?.model ?? agentStreams.get(conversationId)?.model ?? null
const pricingModel = resolveModel(effectiveModel)
const costUSD = calculateCost(pricingModel, inputTokens, outputTokens, 0, 0, 0)
const userQuestion = lastUserMsg.get(conversationId) ?? ''
const assistantText = blobToText(row.user_text)
@ -844,7 +842,8 @@ function parseBubbles(
model: modelForDisplay(effectiveModel),
inputTokens,
outputTokens,
costUSD,
costBasis: 'estimated',
pricingModel,
tools: [
...(hasCode ? ['cursor:edit', ...languages.map(l => `lang:${l}`)] : []),
...(agentTurn?.tools ?? []),
@ -893,7 +892,8 @@ function parseBubbles(
model: modelForDisplay(effectiveModel),
inputTokens,
outputTokens,
costUSD: calculateCost(resolveModel(effectiveModel), inputTokens, outputTokens, 0, 0, 0),
costBasis: 'estimated',
pricingModel: resolveModel(effectiveModel),
tools: stream?.tools ?? [],
bashCommands: stream?.bash ?? [],
timestamp,
@ -919,7 +919,8 @@ function parseBubbles(
model: modelForDisplay(stream.model),
inputTokens,
outputTokens,
costUSD: calculateCost(resolveModel(stream.model), inputTokens, outputTokens, 0, 0, 0),
costBasis: 'estimated',
pricingModel: resolveModel(stream.model),
tools: stream.tools,
bashCommands: stream.bash,
timestamp: agentKvTimestamp,

View file

@ -3,7 +3,6 @@ import { homedir } from 'node:os'
import { basename, dirname, join, resolve } from 'node:path'
import { extractBashCommands } from '../bash-utils.js'
import { calculateCost } from '../models.js'
import type { ParsedProviderCall, ProbeRoot, Provider, SessionParser, SessionSource } from './types.js'
type JsonObject = Record<string, unknown>
@ -315,14 +314,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
cachedInputTokens: cacheReadInputTokens,
reasoningTokens: 0,
webSearchRequests: 0,
costUSD: calculateCost(
realModel,
inputTokens,
outputTokens,
cacheCreationInputTokens,
cacheReadInputTokens,
0,
),
costBasis: 'estimated',
costIsEstimated: true,
tools: pendingTools,
bashCommands: pendingBashCommands,

View file

@ -11,6 +11,7 @@ import {
clearCursorWorkspaceMapCache,
} from '../../src/providers/cursor.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)
@ -105,7 +106,7 @@ function createWorkspaceDir(hash: string, folderUri: string, composerIds: string
async function collect(parser: { parse(): AsyncGenerator<ParsedProviderCall> }): Promise<ParsedProviderCall[]> {
const out: ParsedProviderCall[] = []
for await (const call of parser.parse()) out.push(call)
for await (const call of parser.parse()) out.push(priceProviderCall(call))
return out
}

View file

@ -4,6 +4,7 @@ import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { calculateCost } from '../../src/models.js'
import { priceProviderCall } from '../../src/pricing-pass.js'
import { createKimicodeProvider, kimicode } from '../../src/providers/kimicode.js'
import type { ParsedProviderCall, Provider, SessionSource } from '../../src/providers/types.js'
@ -138,7 +139,7 @@ async function collect(
seenKeys = new Set<string>(),
): Promise<ParsedProviderCall[]> {
const calls: ParsedProviderCall[] = []
for await (const call of provider.createSessionParser(source, seenKeys).parse()) calls.push(call)
for await (const call of provider.createSessionParser(source, seenKeys).parse()) calls.push(priceProviderCall(call))
return calls
}