From a30bfed80e691c8703bd122f83acd52ae6bb597b Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Sun, 26 Jul 2026 09:44:10 -0700 Subject: [PATCH] =?UTF-8?q?refactor(pricing):=20gap=20batch=20=E2=80=94=20?= =?UTF-8?q?cursor,=20kimicode;=20codex=20per=20investigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/providers/codex.ts | 9 +++++++++ src/providers/cursor.ts | 11 ++++++----- src/providers/kimicode.ts | 10 +--------- tests/providers/cursor-workspace-breakdown.test.ts | 3 ++- tests/providers/kimicode.test.ts | 3 ++- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/providers/codex.ts b/src/providers/codex.ts index 5cdce82a..a31fb60f 100644 --- a/src/providers/codex.ts +++ b/src/providers/codex.ts @@ -540,6 +540,11 @@ function createParser(source: SessionSource, seenKeys: Set): 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): 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, diff --git a/src/providers/cursor.ts b/src/providers/cursor.ts index 250e3279..16fc90f2 100644 --- a/src/providers/cursor.ts +++ b/src/providers/cursor.ts @@ -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, diff --git a/src/providers/kimicode.ts b/src/providers/kimicode.ts index b85845a9..d00c0f27 100644 --- a/src/providers/kimicode.ts +++ b/src/providers/kimicode.ts @@ -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 @@ -315,14 +314,7 @@ function createParser(source: SessionSource, seenKeys: Set): SessionPars cachedInputTokens: cacheReadInputTokens, reasoningTokens: 0, webSearchRequests: 0, - costUSD: calculateCost( - realModel, - inputTokens, - outputTokens, - cacheCreationInputTokens, - cacheReadInputTokens, - 0, - ), + costBasis: 'estimated', costIsEstimated: true, tools: pendingTools, bashCommands: pendingBashCommands, diff --git a/tests/providers/cursor-workspace-breakdown.test.ts b/tests/providers/cursor-workspace-breakdown.test.ts index 8e666b41..e2a372f1 100644 --- a/tests/providers/cursor-workspace-breakdown.test.ts +++ b/tests/providers/cursor-workspace-breakdown.test.ts @@ -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 }): Promise { 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 } diff --git a/tests/providers/kimicode.test.ts b/tests/providers/kimicode.test.ts index 2ff426ad..7ce93243 100644 --- a/tests/providers/kimicode.test.ts +++ b/tests/providers/kimicode.test.ts @@ -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(), ): Promise { 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 }