fix(models): price kimi-for-coding by the model it served on the call date (#1676)

* fix(models): price kimi-for-coding by the model it served on the call date

kimi-for-coding is a moving alias. It priced as retired K2 Thinking for
every call. It now prices as K2.5 from 27 Jan 2026, K2.6 from 13 Apr and
K2.7 Code from 12 Jun (K2.8 Preview from 11 Sep has no published price,
so it stays on K2.7 Code). kimi-for-coding-highspeed prices at Moonshot's
K2.7 Code HighSpeed rate instead of $0. Daily cache v64.

* fix(models): mark K2.8 Preview kimi-for-coding calls as estimated

From 11 Sep 2026 kimi-for-coding served K2.8 Preview, which has no
published rate and prices as K2.7 Code. Those calls now carry the
estimated flag. The legacy kimi provider prices by the dated model too.

* fix(models): flag the K2.8 Preview stand-in where every call is read

cachedCallToApiCall now marks kimi-for-coding calls from 11 Sep 2026
estimated, so the flag reaches every provider and cached reads without a
parse bump. Calls with a reported cost are left alone. The kimi provider's
own flag and its parse version entry go, as they only duplicated this.
This commit is contained in:
Resham Joshi 2026-10-08 00:02:38 +02:00 • committed by GitHub
parent d049b38255
commit c826f1a479
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 218 additions and 21 deletions

View file

@ -308,7 +308,13 @@ import type { DateRange, ProjectSummary } from './types.js'
// billed again at the cache rates. Only input tokens and estimated cost fall;
// call counts are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is
// needed.
export const DAILY_CACHE_VERSION = 63
// v64: Kimi Code's `kimi-for-coding` prices by the model it served on the call's
// date (K2.5, K2.6, K2.7 Code) instead of retired K2 Thinking, and
// `kimi-for-coding-highspeed` prices at K2.7 Code HighSpeed instead of $0.
// From 11 Sep 2026 the alias served K2.8 Preview, which has no published rate;
// those calls price as K2.7 Code and are marked estimated.
// Calls are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
export const DAILY_CACHE_VERSION = 64
const MIN_SUPPORTED_VERSION = 28
/// Providers whose per-day CALL COUNT means something different at

View file

@ -131,6 +131,9 @@ const BUILTIN_PRICE_OVERRIDES: Record<string, SnapshotEntry> = {
'composer-2': [0.5e-6, 2.5e-6, 0.5e-6, 0.2e-6],
'composer-1.5': [3.5e-6, 17.5e-6, 3.5e-6, 0.35e-6],
'composer-1': [1.25e-6, 10e-6, 1.25e-6, 0.125e-6],
// Moonshot's published rate (platform.kimi.ai/docs/pricing/chat): $1.90 miss,
// $0.38 hit, $8.00 output. LiteLLM only carries a reseller row for it.
'kimi-k2.7-code-highspeed': [1.9e-6, 8e-6, null, 0.38e-6],
}
// Assemble a ModelCosts, applying the cache-cost heuristics (write = 1.25x
@ -676,8 +679,14 @@ const BUILTIN_ALIASES: Record<string, string> = {
'orcarouter/fusion-flash': 'openai/gpt-oss-120b',
'orcarouter/fusion-mini': 'openai/gpt-oss-120b',
'kimi-auto': 'kimi-k2-thinking',
'kimi-code': 'kimi-k2-thinking',
'kimi-for-coding': 'kimi-k2-thinking',
// `kimi-for-coding` is Kimi Code's moving alias; `kimi-code` is kimi-cli's
// spelling of the same SKU. pricingModelAt prices older calls by the model
// the alias served then. K2.8 Preview (11 Sep 2026 on) has no Open Platform
// price, so it stays on K2.7 Code's.
'kimi-code': 'kimi-k2.7-code',
'kimi-for-coding': 'kimi-k2.7-code',
// HighSpeed has been K2.7 Code HighSpeed since it launched on 9 Jul 2026.
'kimi-for-coding-highspeed': 'kimi-k2.7-code-highspeed',
// Kimi Code wires report the bare `k3` id in llm.request.model; without an
// alias those calls priced at $0 and the provider looked absent in the UI.
'k3': 'kimi-k3',
@ -989,7 +998,6 @@ export function isBuiltInFlatRateModel(model: string): boolean {
if (
leaf === 'auto'
|| leaf === 'auto-genius'
|| leaf === 'kimi-for-coding-highspeed'
) return true
if (leaf.startsWith('grok-composer-')) return true
if (leaf.startsWith('warp-auto-')) return true
@ -1244,13 +1252,39 @@ function stripKnownPricingVariantSuffix(model: string): string | null {
}
const AUTO_REVIEW_LUNA_FROM = Date.parse('2026-07-30T00:00:00Z')
// kimi-cli labelled the alias "powered by kimi-k2.5" from 27 Jan 2026 (1.2) and
// dropped that on 13 Apr 2026 (#1860) as K2.6 rolled out; Kimi Code's What's
// New dates K2.7 Code to 12 Jun 2026. What it served before K2.5 is unsourced,
// so those calls keep the K2 Thinking rate they always had.
const KIMI_CODING_K2_5_FROM = Date.parse('2026-01-27T00:00:00Z')
const KIMI_CODING_K2_6_FROM = Date.parse('2026-04-13T00:00:00Z')
const KIMI_CODING_K2_7_FROM = Date.parse('2026-06-12T00:00:00Z')
const KIMI_CODING_K2_8_FROM = Date.parse('2026-09-11T00:00:00Z')
/// The model a call is priced by. Only `codex-auto-review` depends on the
/// call's date (see BUILTIN_ALIASES); a user alias for it still wins, and a
/// missing or unparseable timestamp keeps the forward default.
/// The model a call is priced by. Only `codex-auto-review` and the Kimi Code
/// alias depend on the call's date (see BUILTIN_ALIASES); a user alias for
/// them still wins, and a missing or unparseable timestamp keeps the forward
/// default.
export function pricingModelAt(model: string, timestamp: string | undefined): string {
if (model.toLowerCase() !== 'codex-auto-review' || Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model
return Date.parse(timestamp ?? '') < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model
const id = model.toLowerCase()
if (id !== 'codex-auto-review' && id !== 'kimi-for-coding' && id !== 'kimi-code') return model
if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model
const at = Date.parse(timestamp ?? '')
if (id === 'codex-auto-review') return at < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model
if (at < KIMI_CODING_K2_5_FROM) return 'kimi-k2-thinking'
if (at < KIMI_CODING_K2_6_FROM) return 'kimi-k2.5'
return at < KIMI_CODING_K2_7_FROM ? 'kimi-k2.6' : model
}
/// True when pricingModelAt stands in for a model with no published rate:
/// Kimi Code's alias served K2.8 Preview from 11 Sep 2026, priced as K2.7 Code.
/// A missing or unparseable timestamp gets the forward default, so it counts.
export function isStandInPricedAt(model: string, timestamp: string | undefined): boolean {
const id = model.toLowerCase()
if (id !== 'kimi-for-coding' && id !== 'kimi-code') return false
if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return false
const at = Date.parse(timestamp ?? '')
return !(at < KIMI_CODING_K2_8_FROM)
}
export function getModelCosts(model: string): ModelCosts | null {
@ -1548,6 +1582,8 @@ const autoModelNames: Record<string, string> = {
'openclaw-auto': 'OpenClaw (auto)',
'qwen-auto': 'Qwen (auto)',
'kimi-auto': 'Kimi (auto)',
'kimi-for-coding': 'Kimi for Coding',
'kimi-for-coding-highspeed': 'Kimi for Coding HighSpeed',
'codex-auto-review': 'Codex Auto Review',
'gpt-reserve': 'Luna Reserve',
}

View file

@ -5,7 +5,7 @@ import { createHash } from 'crypto'
import { performance } from 'node:perf_hooks'
import { basename, dirname, join, resolve, sep } from 'path'
import { FS_SCAN_CONCURRENCY, mapWithConcurrency, readSessionLines } from './fs-utils.js'
import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback, getModelRoute } from './models.js'
import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback, getModelRoute, isStandInPricedAt } from './models.js'
import { resolveSubagentAttribution, sessionIdentity } from './sessions-report.js'
import { normalizeContentBlocks, flatSlice, flatString } from './content-utils.js'
import { discoverAllSessions, discoverAllSessionsWithFailures, getProvider } from './providers/index.js'
@ -2869,7 +2869,7 @@ function cachedCallToApiCall(call: CachedCall): ParsedApiCall {
webSearchRequests: u.webSearchRequests,
},
costUSD: call.costUSD ?? recordedCostFallback(call.model, costUSD, call.fallbackCostUSD) ?? costUSD,
isEstimated: call.isEstimated,
isEstimated: call.isEstimated || (call.costUSD === undefined && isStandInPricedAt(call.model, call.timestamp)) || undefined,
tools: call.tools,
mcpTools: extractMcpTools(call.tools),
skills: call.skills,

View file

@ -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 { calculateCost, getShortModelName, pricingModelAt } from '../models.js'
import type { ProbeRoot, ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
type JsonObject = Record<string, unknown>
@ -298,7 +298,7 @@ function createParser(source: SessionSource, shareDir: string, seenKeys: Set<str
const model = stringField(envelope.payload, 'model') ?? stringField(envelope.payload, 'model_name') ?? configuredModel
const costUSD = calculateCost(
model,
pricingModelAt(model, envelope.timestamp),
usage.inputTokens,
usage.outputTokens,
usage.cacheCreationInputTokens,

View file

@ -3,7 +3,7 @@ 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 { calculateCost, pricingModelAt } from '../models.js'
import { FS_SCAN_CONCURRENCY, mapWithConcurrency } from '../fs-utils.js'
import type { ParsedProviderCall, ProbeRoot, Provider, SessionParser, SessionSource } from './types.js'
@ -377,7 +377,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
reasoningTokens: 0,
webSearchRequests: 0,
costUSD: calculateCost(
realModel,
pricingModelAt(realModel, timestamp),
inputTokens,
outputTokens,
cacheCreationInputTokens,

View file

@ -0,0 +1,57 @@
// The K2.8 Preview stand-in flag is set where every call is read
// (cachedCallToApiCall), so it reaches calls from any provider, cold or warm.
// Own file because the codex provider captures CODEX_HOME at import.
import { afterAll, beforeEach, expect, it, vi } from 'vitest'
import { mkdir, rm, writeFile } from 'fs/promises'
import { join } from 'path'
const testRoot = vi.hoisted(() => {
const root = `${process.env['TMPDIR'] || '/tmp'}/kimi-stand-in-${process.pid}-${Date.now()}`
process.env['HOME'] = `${root}/home`
process.env['USERPROFILE'] = `${root}/home`
process.env['CODEX_HOME'] = `${root}/codex`
return root
})
const CODEX_HOME = join(testRoot, 'codex')
const CACHE_DIR = join(testRoot, 'cache')
beforeEach(() => {
process.env['HOME'] = join(testRoot, 'home')
process.env['USERPROFILE'] = join(testRoot, 'home')
process.env['CODEX_HOME'] = CODEX_HOME
process.env['CODEBURN_CACHE_DIR'] = CACHE_DIR
})
afterAll(async () => {
await rm(testRoot, { recursive: true, force: true })
})
function rollout(id: string, day: string): string {
const usage = { input_tokens: 1000, cached_input_tokens: 0, output_tokens: 100, reasoning_output_tokens: 0, total_tokens: 1100 }
return [
JSON.stringify({ type: 'session_meta', timestamp: `${day}T10:00:00Z`, payload: { session_id: id, model: 'kimi-for-coding', cwd: '/Users/test/proj', originator: 'codex_cli_rs' } }),
JSON.stringify({ type: 'response_item', timestamp: `${day}T10:00:10Z`, payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'hi' }] } }),
JSON.stringify({ type: 'event_msg', timestamp: `${day}T10:01:00Z`, payload: { type: 'token_count', info: { model: 'kimi-for-coding', last_token_usage: usage, total_token_usage: usage } } }),
].join('\n') + '\n'
}
it('marks kimi-for-coding calls from 11 Sep 2026 estimated on a cold parse and a cache-rehydrated read', async () => {
await mkdir(join(CODEX_HOME, 'sessions', '2026', '09', '10'), { recursive: true })
await mkdir(join(CODEX_HOME, 'sessions', '2026', '09', '12'), { recursive: true })
await mkdir(CACHE_DIR, { recursive: true })
await writeFile(join(CODEX_HOME, 'sessions', '2026', '09', '10', 'rollout-k27.jsonl'), rollout('k27', '2026-09-10'))
await writeFile(join(CODEX_HOME, 'sessions', '2026', '09', '12', 'rollout-k28.jsonl'), rollout('k28', '2026-09-12'))
const { clearSessionCache, parseAllSessions } = await import('../src/parser.js')
const flags = (projects: Awaited<ReturnType<typeof parseAllSessions>>) => projects
.flatMap(p => p.sessions).flatMap(s => s.turns).flatMap(t => t.assistantCalls)
.map(c => [c.timestamp.slice(0, 10), c.isEstimated ?? false])
.sort()
clearSessionCache()
expect(flags(await parseAllSessions(undefined, 'codex'))).toEqual([['2026-09-10', false], ['2026-09-12', true]])
clearSessionCache()
expect(flags(await parseAllSessions(undefined, 'codex'))).toEqual([['2026-09-10', false], ['2026-09-12', true]])
})

View file

@ -208,7 +208,7 @@ describe('buildMenubarPayload', () => {
const kimiK3 = payload.current.topModels.find(m => m.name === 'Kimi K3')!
expect(kimiK3.cost).toBeCloseTo(2.5 + 0.5 + 1.2)
expect(kimiK3.calls).toBe(78 + 2 + 40)
const k2 = payload.current.topModels.find(m => m.name === 'Kimi K2 Thinking')!
const k2 = payload.current.topModels.find(m => m.name === 'Kimi for Coding')!
expect(k2.cost).toBeCloseTo(0.06)
expect(payload.current.topModels.find(m => m.name === 'k3')).toBeUndefined()
expect(payload.current.topModels.find(m => m.name === 'k3-agent')).toBeUndefined()

View file

@ -92,7 +92,7 @@ describe('post-hoist resolution stability', () => {
it('kimi managed aliases resolve to priced Kimi models', () => {
expect(getShortModelName('kimi-auto')).toBe('Kimi (auto)')
expect(getShortModelName('kimi-for-coding')).toBe('Kimi K2 Thinking')
expect(getShortModelName('kimi-for-coding')).toBe('Kimi for Coding')
expect(getShortModelName('kimi-k2-thinking-turbo')).toBe('Kimi K2 Thinking Turbo')
expect(getShortModelName('kimi-k2.6')).toBe('Kimi K2.6')
expect(getModelCosts('kimi-auto')?.inputCostPerToken).toBeGreaterThan(0)

View file

@ -30,6 +30,7 @@ import {
snapshotPricingState,
restorePricingState,
pricingModelAt,
isStandInPricedAt,
} from '../src/models.js'
import { getDailyCacheConfigHash } from '../src/usage-aggregator.js'
import snapshotData from '../src/data/litellm-snapshot.json' with { type: 'json' }
@ -572,7 +573,7 @@ describe('resolveCanonicalModelId', () => {
expect(resolveCanonicalModelId('gpt-5-fast')).toBe('gpt-5')
expect(resolveCanonicalModelId('gpt-5-untracked-xyz')).toBe('gpt-5-untracked-xyz')
expect(resolveCanonicalModelId('claude-opus-4.6')).toBe('claude-opus-4-6')
expect(resolveCanonicalModelId('kimi-code')).toBe('kimi-k2-thinking')
expect(resolveCanonicalModelId('kimi-code')).toBe('kimi-k2.7-code')
expect(resolveCanonicalModelId('cline-pass/kimi-k3')).toBe('kimi-k3')
expect(resolveCanonicalModelId('orcarouter/auto')).not.toBe(resolveCanonicalModelId('claude-sonnet-4-5'))
expect(resolveCanonicalModelId('orcarouter/fusion')).toBe(resolveCanonicalModelId('openai/gpt-oss-120b'))
@ -1196,6 +1197,79 @@ describe('Cursor model variants resolve to pricing', () => {
})
})
describe('Kimi Code moving alias', () => {
const rates = (model: string) => {
const c = getModelCosts(model)!
return [c.inputCostPerToken * 1e6, c.cacheReadCostPerToken * 1e6, c.outputCostPerToken * 1e6].map(v => +v.toFixed(4))
}
it('prices kimi-for-coding by the model it served on the call date', () => {
expect(pricingModelAt('kimi-for-coding', '2026-01-26T23:59:59.999Z')).toBe('kimi-k2-thinking')
expect(pricingModelAt('kimi-for-coding', '2026-01-27T00:00:00.000Z')).toBe('kimi-k2.5')
expect(pricingModelAt('kimi-for-coding', '2026-04-12T23:59:59.999Z')).toBe('kimi-k2.5')
expect(pricingModelAt('kimi-for-coding', '2026-04-13T00:00:00.000Z')).toBe('kimi-k2.6')
expect(pricingModelAt('kimi-for-coding', '2026-06-11T23:59:59.999Z')).toBe('kimi-k2.6')
expect(pricingModelAt('kimi-for-coding', '2026-06-12T00:00:00.000Z')).toBe('kimi-for-coding')
expect(pricingModelAt('kimi-for-coding', '2026-09-27T10:00:00Z')).toBe('kimi-for-coding')
expect(pricingModelAt('kimi-code', '2026-03-01T00:00:00Z')).toBe('kimi-k2.5')
expect(pricingModelAt('kimi-for-coding', undefined)).toBe('kimi-for-coding')
expect(pricingModelAt('kimi-for-coding', 'not a date')).toBe('kimi-for-coding')
expect(pricingModelAt('kimi-for-coding-highspeed', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding-highspeed')
expect(pricingModelAt('k3', '2026-03-01T00:00:00Z')).toBe('k3')
})
it('resolves each period to Moonshot list prices ($/M input, cache hit, output)', () => {
expect(rates('kimi-k2.5')).toEqual([0.6, 0.1, 3])
expect(rates('kimi-k2.6')).toEqual([0.95, 0.16, 4])
expect(rates('kimi-for-coding')).toEqual([0.95, 0.19, 4])
expect(rates('kimi-code')).toEqual([0.95, 0.19, 4])
expect(rates('kimi-for-coding-highspeed')).toEqual([1.9, 0.38, 8])
expect(rates('k3')).toEqual([3, 0.3, 15])
})
it('treats highspeed as priced, not a flat-rate SKU', () => {
expect(isFlatRateModel('kimi-for-coding-highspeed')).toBe(false)
expect(calculateCost('kimi-for-coding-highspeed', 1_000_000, 1_000_000, 0, 1_000_000, 0)).toBeCloseTo(10.28)
})
it('marks only the K2.8 Preview period (from 11 Sep 2026) as stand-in priced', () => {
expect(isStandInPricedAt('kimi-for-coding', '2026-09-10T23:59:59.999Z')).toBe(false)
expect(isStandInPricedAt('kimi-for-coding', '2026-09-11T00:00:00.000Z')).toBe(true)
expect(isStandInPricedAt('kimi-code', '2026-09-27T10:00:00Z')).toBe(true)
expect(isStandInPricedAt('kimi-for-coding', '2026-07-01T00:00:00Z')).toBe(false)
expect(isStandInPricedAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe(false)
expect(isStandInPricedAt('kimi-for-coding', undefined)).toBe(true)
expect(isStandInPricedAt('kimi-for-coding-highspeed', '2026-09-27T10:00:00Z')).toBe(false)
expect(isStandInPricedAt('k3', '2026-09-27T10:00:00Z')).toBe(false)
setModelAliases({ 'kimi-for-coding': 'kimi-k3' })
try {
expect(isStandInPricedAt('kimi-for-coding', '2026-09-27T10:00:00Z')).toBe(false)
} finally {
setModelAliases({})
}
})
it('keeps the alias name for display', () => {
expect(getShortModelName('kimi-for-coding')).toBe('Kimi for Coding')
expect(getShortModelName('kimi-for-coding-highspeed')).toBe('Kimi for Coding HighSpeed')
})
it('lets a user alias or price override win over the date rule', () => {
setModelAliases({ 'kimi-for-coding': 'kimi-k3' })
try {
expect(pricingModelAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding')
} finally {
setModelAliases({})
}
setPriceOverrides({ 'kimi-for-coding': { input: 1, output: 2 } })
try {
expect(pricingModelAt('kimi-for-coding', '2026-03-01T00:00:00Z')).toBe('kimi-for-coding')
} finally {
setPriceOverrides({})
}
})
})
describe('Codex activity ids (#1047)', () => {
it('keeps the activity label instead of collapsing to the underlying model name', () => {
expect(getShortModelName('codex-auto-review')).toBe('Codex Auto Review')
@ -1786,8 +1860,6 @@ describe('findUnpricedModels', () => {
{ model: 'auto-genius', calls: 898, cost: 0, tokens: 35_300_000 },
{ model: 'cline-pass/auto-genius', calls: 4, cost: 0, tokens: 33_900 },
{ model: 'auto', calls: 449, cost: 0, tokens: 17_700_000 },
{ model: 'kimi-for-coding-highspeed', calls: 12, cost: 0, tokens: 3_400_000 },
{ model: 'moonshot/kimi-for-coding-highspeed', calls: 2, cost: 0, tokens: 80_000 },
{ model: 'grok-composer-2.5-fast', calls: 10, cost: 0, tokens: 1_900_000 },
{ model: 'Grok Composer 2.5 Fast', calls: 10, cost: 0, tokens: 1_900_000 },
{ model: 'Warp Auto (efficient)', calls: 3, cost: 0, tokens: 50_000 },
@ -1827,7 +1899,7 @@ describe('findUnpricedModels', () => {
expect(isExpectedFreeModel('warp-auto-efficient')).toBe(false)
expect(isExpectedFreeModel('auto-genius')).toBe(true)
expect(isExpectedFreeModel('auto')).toBe(true)
expect(isExpectedFreeModel('kimi-for-coding-highspeed')).toBe(true)
expect(isExpectedFreeModel('kimi-for-coding-highspeed')).toBe(false)
expect(isExpectedFreeModel('warp')).toBe(false)
expect(isExpectedFreeModel('codex-auto-review')).toBe(false)
expect(isExpectedFreeModel('zz-mystery-paid-model-999')).toBe(false)

View file

@ -125,6 +125,32 @@ describe('Kimi provider', () => {
expect(calls[0]!.costUSD).toBeGreaterThan(0)
})
it('prices kimi-for-coding by call date', async () => {
const wirePath = await writeSession('/Users/test/work/app', 'sess-k28', [
record(1789084799, 'StatusUpdate', {
message_id: 'before',
model_name: 'kimi-for-coding',
token_usage: { input_other: 1_000_000, output: 1_000_000 },
}),
record(1789084800, 'StatusUpdate', {
message_id: 'from',
model_name: 'kimi-for-coding',
token_usage: { input_other: 1_000_000, output: 1_000_000 },
}),
record(1777593600, 'StatusUpdate', {
message_id: 'k26',
model_name: 'kimi-for-coding',
token_usage: { input_other: 1_000_000, output: 0 },
}),
])
const calls = await collect(createKimiProvider(tmpDir), wirePath)
expect(calls[0]!.costUSD).toBeCloseTo(4.95)
expect(calls[1]!.costUSD).toBeCloseTo(4.95)
expect(calls[2]!.costUSD).toBeCloseTo(0.95)
})
it('uses content parts, model payload overrides, and message-id deduplication', async () => {
process.env.KIMI_MODEL_NAME = 'kimi-k2-thinking'
const wirePath = await writeSession('/Users/test/work/app', 'sess-2', [