Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/daily-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,10 @@ import type { DateRange, ProjectSummary } from './types.js'
// copilotCredits, and a chat session that also ran on the agent host is left
// to it. That twin drops calls, so copilot joins
// PENDING_REDERIVE_PROVIDER_VERSIONS at 75.
export const DAILY_CACHE_VERSION = 75
// v77: Warp conversations that carry only total_billed_cost_in_cents are
// priced from it instead of credits or the token floor. Calls are unchanged,
// so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
export const DAILY_CACHE_VERSION = 77
const MIN_SUPPORTED_VERSION = 28

/// Providers whose per-day CALL COUNT means something different at
Expand Down
11 changes: 9 additions & 2 deletions src/providers/warp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ type WarpConversationData = {
total_provider_cost_in_cents?: number
// Cumulative per-category charged usage across the conversation, in cents.
total_charged_usage?: WarpChargedUsageTotals
// Server snapshot of what the customer was billed, in US cents. Replaced
// total_provider_cost_in_cents in Warp 0.2026.10 (warpdotdev/warp#16327).
total_billed_cost_in_cents?: number
// Credits Warp deducted for the conversation (always populated today).
credits_spent?: number
}
Expand Down Expand Up @@ -216,8 +219,9 @@ function extractTokenBudget(rawConversationData: string): { tokenBudget: number;
// least authoritative:
// (a) total_provider_cost_in_cents — server-authoritative USD (exact)
// (b) sum of total_charged_usage — server per-category USD (exact)
// (c) credits_spent × CREDIT_USD_RATE — credits at published rate (heuristic)
// (d) null → caller falls back to the token × LiteLLM estimate (floor)
// (c) total_billed_cost_in_cents — server billed snapshot USD (exact)
// (d) credits_spent × CREDIT_USD_RATE — credits at published rate (heuristic)
// (e) null → caller falls back to the token × LiteLLM estimate (floor)
// A value of 0/absent at a rung is treated as "not recorded here" and defers to
// the next rung, so a zero never masks a real cost recorded elsewhere.
function conversationCostUsd(rawConversationData: string): WarpCost | null {
Expand All @@ -244,6 +248,9 @@ function conversationCostUsd(rawConversationData: string): WarpCost | null {
if (cents > 0) return { usd: cents / 100, estimated: false }
}

const billedCents = safeNumber(meta.total_billed_cost_in_cents)
if (billedCents > 0) return { usd: billedCents / 100, estimated: false }

const credits = safeNumber(meta.credits_spent)
if (credits > 0) return { usd: credits * CREDIT_USD_RATE, estimated: true }

Expand Down
4 changes: 3 additions & 1 deletion src/session-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,9 @@ export const PROVIDER_PARSE_VERSIONS: Record<string, string> = {
// cached before this hold costUSD: undefined and are re-priced from the
// token floor on every read, so they must re-parse once for the real dollars
// to land.
warp: 'worktree-project-grouping-v1-est-cost-billing-cost-v1',
// billed-cost-v1: total_billed_cost_in_cents prices conversations that carry
// no provider cost or charged usage.
warp: 'worktree-project-grouping-v1-est-cost-billing-cost-v1-billed-cost-v1',
// cache-read-v1-est-cost: gen_metadata and RPC usage now carry cache-read
// tokens, fields 9/10 read as thinking/response (they were swapped), and
// placeholder-only models are priced and flagged costIsEstimated.
Expand Down
37 changes: 37 additions & 0 deletions tests/providers/warp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,4 +469,41 @@ skipUnlessSqlite('warp provider', () => {
expect(totalCost).toBeCloseTo(0.05, 6)
expect(calls[0]!.costIsEstimated).toBe(false)
})

it.each([
['only total_billed_cost_in_cents', { total_billed_cost_in_cents: 3.5 }, 0.035],
['only legacy total_provider_cost_in_cents', { total_provider_cost_in_cents: 2.5 }, 0.025],
['both cost fields, legacy provider cost first', { total_provider_cost_in_cents: 2.5, total_billed_cost_in_cents: 3.5 }, 0.025],
['charged usage ahead of the billed snapshot', { total_billed_cost_in_cents: 3.5, total_charged_usage: { input_cost_in_cents: 4 } }, 0.04],
])('prices from %s', async (_label, costFields, expectedUsd) => {
const dbPath = createWarpDb(tmpDir)
withTestDb(dbPath, (db) => {
insertConversation(db, 'conv-billed', {
conversation_usage_metadata: {
...costFields,
credits_spent: 1.44165,
token_usage: [
{
model_id: 'Claude Haiku 4.5',
warp_tokens: 51385,
byok_tokens: 0,
warp_token_usage_by_category: { primary_agent: 51385 },
byok_token_usage_by_category: {},
},
],
},
})
insertQuery(db, {
exchangeId: 'ex-1',
conversationId: 'conv-billed',
startTs: '2026-10-18 10:00:00.000000',
input: JSON.stringify([{ Query: { text: 'do the thing' } }]),
})
})

const calls = await collectCalls(dbPath, 'conv-billed')
expect(calls).toHaveLength(1)
expect(calls[0]!.costUSD).toBeCloseTo(expectedUsd, 6)
expect(calls[0]!.costIsEstimated).toBe(false)
})
})
Loading