fix(models): refresh model pricing
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Refresh display pricing for the current Command Code model catalog, remove expired Qwen promotional rates, add current free and discounted models, and require review when temporary prices expire.
|
||||||
- Use the host-provided `pi-ai` and `pi-coding-agent` core packages instead of installing private runtime copies, including for local and out-of-store development checkouts.
|
- Use the host-provided `pi-ai` and `pi-coding-agent` core packages instead of installing private runtime copies, including for local and out-of-store development checkouts.
|
||||||
|
|
||||||
## 0.4.4 - 2026-08-03
|
## 0.4.4 - 2026-08-03
|
||||||
|
|||||||
@@ -20,57 +20,13 @@ import { COMMAND_CODE_CLI_VERSION, createStreamCommandCode, DEFAULT_API_BASE } f
|
|||||||
import { calculateCommandCodeCost } from "./src/cost.ts"
|
import { calculateCommandCodeCost } from "./src/cost.ts"
|
||||||
import { DEFAULT_MODELS_URL, loadCommandCodeModels } from "./src/models.ts"
|
import { DEFAULT_MODELS_URL, loadCommandCodeModels } from "./src/models.ts"
|
||||||
import { getApiKey, login, refreshToken } from "./src/oauth.ts"
|
import { getApiKey, login, refreshToken } from "./src/oauth.ts"
|
||||||
|
import { MODEL_COSTS, ZERO_MODEL_COST } from "./src/pricing.ts"
|
||||||
|
|
||||||
const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
|
const API_BASE = process.env.COMMANDCODE_API_BASE ?? DEFAULT_API_BASE
|
||||||
const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
|
const MODELS_URL = process.env.COMMANDCODE_MODELS_URL ?? DEFAULT_MODELS_URL
|
||||||
const MODELS_CACHE_PATH =
|
const MODELS_CACHE_PATH =
|
||||||
process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json")
|
process.env.COMMANDCODE_MODELS_CACHE ?? join(getAgentDir(), "commandcode-models.json")
|
||||||
|
|
||||||
type CommandCodeModelCost = {
|
|
||||||
input: number
|
|
||||||
output: number
|
|
||||||
cacheRead: number
|
|
||||||
cacheWrite: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZERO_MODEL_COST: CommandCodeModelCost = {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Provider API supplies the current model list. Keep known display pricing
|
|
||||||
// here until the Provider API exposes prices directly.
|
|
||||||
const MODEL_COSTS: Record<string, CommandCodeModelCost> = {
|
|
||||||
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
||||||
"claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
||||||
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
||||||
"claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
||||||
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
|
||||||
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
|
||||||
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
|
||||||
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
|
||||||
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
|
|
||||||
"google/gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cacheRead: 0.03, cacheWrite: 0 },
|
|
||||||
// 4× usage deal: 75% off (permanent, no expiry)
|
|
||||||
"deepseek/deepseek-v4-pro": { input: 0.435, output: 0.87, cacheRead: 0.003625, cacheWrite: 0 },
|
|
||||||
"deepseek/deepseek-v4-flash": { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
|
|
||||||
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
|
||||||
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
||||||
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
|
||||||
"zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
|
||||||
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
|
||||||
"MiniMaxAI/MiniMax-M2.5": { input: 0.27, output: 0.95, cacheRead: 0.03, cacheWrite: 0 },
|
|
||||||
"Qwen/Qwen3.6-Max-Preview": { input: 1.3, output: 7.8, cacheRead: 0.26, cacheWrite: 1.63 },
|
|
||||||
"Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
|
||||||
// 2× usage deal: 50% off through June 22, 2026
|
|
||||||
"Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
|
|
||||||
"stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
|
|
||||||
"xiaomi/mimo-v2.5-pro": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
||||||
"xiaomi/mimo-v2.5": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamCommandCode = createStreamCommandCode({
|
const streamCommandCode = createStreamCommandCode({
|
||||||
createStream: () => new AssistantMessageEventStream(),
|
createStream: () => new AssistantMessageEventStream(),
|
||||||
calculateCost: calculateCommandCodeCost,
|
calculateCost: calculateCommandCodeCost,
|
||||||
|
|||||||
+16
-4
@@ -10,10 +10,22 @@
|
|||||||
import type { ModelLike, Usage } from "./types.ts"
|
import type { ModelLike, Usage } from "./types.ts"
|
||||||
|
|
||||||
export function calculateCommandCodeCost(model: ModelLike, usage: Usage): void {
|
export function calculateCommandCodeCost(model: ModelLike, usage: Usage): void {
|
||||||
usage.cost.input = (model.cost.input / 1_000_000) * usage.input
|
const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite
|
||||||
usage.cost.output = (model.cost.output / 1_000_000) * usage.output
|
let rates = model.cost
|
||||||
usage.cost.cacheRead = (model.cost.cacheRead / 1_000_000) * usage.cacheRead
|
let matchedThreshold = -1
|
||||||
usage.cost.cacheWrite = (model.cost.cacheWrite * usage.cacheWrite) / 1_000_000
|
for (const tier of model.cost.tiers ?? []) {
|
||||||
|
if (inputTokens > tier.inputTokensAbove && tier.inputTokensAbove > matchedThreshold) {
|
||||||
|
rates = tier
|
||||||
|
matchedThreshold = tier.inputTokensAbove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const longWrite = usage.cacheWrite1h ?? 0
|
||||||
|
const shortWrite = usage.cacheWrite - longWrite
|
||||||
|
usage.cost.input = (rates.input / 1_000_000) * usage.input
|
||||||
|
usage.cost.output = (rates.output / 1_000_000) * usage.output
|
||||||
|
usage.cost.cacheRead = (rates.cacheRead / 1_000_000) * usage.cacheRead
|
||||||
|
usage.cost.cacheWrite = (rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1_000_000
|
||||||
usage.cost.total =
|
usage.cost.total =
|
||||||
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite
|
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite
|
||||||
}
|
}
|
||||||
|
|||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
export interface CommandCodeModelCostRates {
|
||||||
|
input: number
|
||||||
|
output: number
|
||||||
|
cacheRead: number
|
||||||
|
cacheWrite: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandCodeModelCostTier extends CommandCodeModelCostRates {
|
||||||
|
inputTokensAbove: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandCodeModelCost extends CommandCodeModelCostRates {
|
||||||
|
tiers?: readonly CommandCodeModelCostTier[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemporaryPricing {
|
||||||
|
models: readonly string[]
|
||||||
|
expiresOn: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PRICING_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits"
|
||||||
|
export const PRICING_LAST_VERIFIED = "2026-08-04"
|
||||||
|
|
||||||
|
export const ZERO_MODEL_COST: CommandCodeModelCost = {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display prices in USD per million tokens.
|
||||||
|
*
|
||||||
|
* Context-dependent rates use pi's request-wide input pricing tiers. The
|
||||||
|
* highest threshold exceeded by input + cache reads + cache writes applies to
|
||||||
|
* the full request. The Command Code usage page remains authoritative for the
|
||||||
|
* amount billed for an individual request.
|
||||||
|
*/
|
||||||
|
export const MODEL_COSTS: Readonly<Record<string, CommandCodeModelCost>> = {
|
||||||
|
// Free models
|
||||||
|
"poolside/laguna-s-2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
"inclusionai/ling-3.0-flash-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
|
||||||
|
// Open and open-weight models
|
||||||
|
"tencent/hy3-paid": { input: 0.14, output: 0.58, cacheRead: 0.035, cacheWrite: 0 },
|
||||||
|
"moonshotai/Kimi-K3": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
|
||||||
|
"moonshotai/Kimi-K2.7-Code": { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0 },
|
||||||
|
"moonshotai/Kimi-K2.7-Code-Highspeed": {
|
||||||
|
input: 1.9,
|
||||||
|
output: 8,
|
||||||
|
cacheRead: 0.38,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"moonshotai/Kimi-K2.6": { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
||||||
|
"moonshotai/Kimi-K2.5": { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
||||||
|
"zai-org/GLM-5.2": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
||||||
|
"zai-org/GLM-5.2-Fast": { input: 3, output: 10.25, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
|
"zai-org/GLM-5.1": { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 },
|
||||||
|
"zai-org/GLM-5": { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
|
||||||
|
"MiniMaxAI/MiniMax-M3": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
||||||
|
"MiniMaxAI/MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0 },
|
||||||
|
"MiniMaxAI/MiniMax-M2.5": { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
|
||||||
|
// Permanent 75% discount.
|
||||||
|
"deepseek/deepseek-v4-pro": {
|
||||||
|
input: 0.435,
|
||||||
|
output: 0.87,
|
||||||
|
cacheRead: 0.003625,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"deepseek/deepseek-v4-flash": {
|
||||||
|
input: 0.14,
|
||||||
|
output: 0.28,
|
||||||
|
cacheRead: 0.0028,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"Qwen/Qwen3.8-Max": { input: 2, output: 6, cacheRead: 0.25, cacheWrite: 2.5 },
|
||||||
|
"Qwen/Qwen3.7-Max": { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.13 },
|
||||||
|
"Qwen/Qwen3.7-Plus": {
|
||||||
|
input: 0.4,
|
||||||
|
output: 1.6,
|
||||||
|
cacheRead: 0.08,
|
||||||
|
cacheWrite: 0.5,
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
inputTokensAbove: 256_000,
|
||||||
|
input: 1.2,
|
||||||
|
output: 4.8,
|
||||||
|
cacheRead: 0.24,
|
||||||
|
cacheWrite: 1.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwen/Qwen3.7-Flash": {
|
||||||
|
input: 0.03,
|
||||||
|
output: 0.13,
|
||||||
|
cacheRead: 0.006,
|
||||||
|
cacheWrite: 0.038,
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
inputTokensAbove: 32_000,
|
||||||
|
input: 0.1,
|
||||||
|
output: 0.4,
|
||||||
|
cacheRead: 0.02,
|
||||||
|
cacheWrite: 0.125,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputTokensAbove: 256_000,
|
||||||
|
input: 0.2,
|
||||||
|
output: 0.8,
|
||||||
|
cacheRead: 0.04,
|
||||||
|
cacheWrite: 0.25,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwen/Qwen3.6-Max-Preview": {
|
||||||
|
input: 1.3,
|
||||||
|
output: 7.8,
|
||||||
|
cacheRead: 0.26,
|
||||||
|
cacheWrite: 1.63,
|
||||||
|
},
|
||||||
|
"Qwen/Qwen3.6-Plus": { input: 0.5, output: 3, cacheRead: 0.1, cacheWrite: 0 },
|
||||||
|
"stepfun/Step-3.7-Flash": { input: 0.2, output: 1.15, cacheRead: 0.04, cacheWrite: 0 },
|
||||||
|
"stepfun/Step-3.5-Flash": { input: 0.1, output: 0.3, cacheRead: 0.02, cacheWrite: 0 },
|
||||||
|
// Permanent discounted rates.
|
||||||
|
"xiaomi/mimo-v2.5-pro": { input: 0.435, output: 0.87, cacheRead: 0.0036, cacheWrite: 0 },
|
||||||
|
"xiaomi/mimo-v2.5": { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
||||||
|
"nvidia/nemotron-3-ultra-550b-a55b": {
|
||||||
|
input: 0.6,
|
||||||
|
output: 2.4,
|
||||||
|
cacheRead: 0.12,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"sakana/fugu-ultra": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
|
"thinkingmachines/inkling": { input: 1, output: 4.05, cacheRead: 0.17, cacheWrite: 0 },
|
||||||
|
"thinkingmachines/inkling-small": {
|
||||||
|
input: 0.5,
|
||||||
|
output: 1.2,
|
||||||
|
cacheRead: 0.1,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"meta/muse-spark-1.1": { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
||||||
|
|
||||||
|
// Anthropic
|
||||||
|
// Introductory pricing through 2026-08-31.
|
||||||
|
"claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
||||||
|
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
||||||
|
"claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
||||||
|
"claude-opus-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||||
|
"claude-opus-4-8": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||||
|
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||||
|
"claude-haiku-4-5-20251001": {
|
||||||
|
input: 1,
|
||||||
|
output: 5,
|
||||||
|
cacheRead: 0.1,
|
||||||
|
cacheWrite: 1.25,
|
||||||
|
},
|
||||||
|
|
||||||
|
// OpenAI
|
||||||
|
"gpt-5.6-sol": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||||
|
// Discounted rates through 2026-08-14.
|
||||||
|
"gpt-5.6-terra": {
|
||||||
|
input: 1,
|
||||||
|
output: 6,
|
||||||
|
cacheRead: 0.1,
|
||||||
|
cacheWrite: 1.25,
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
inputTokensAbove: 272_000,
|
||||||
|
input: 2,
|
||||||
|
output: 9,
|
||||||
|
cacheRead: 0.2,
|
||||||
|
cacheWrite: 2.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"gpt-5.6-luna": {
|
||||||
|
input: 0.1,
|
||||||
|
output: 0.6,
|
||||||
|
cacheRead: 0.01,
|
||||||
|
cacheWrite: 0.125,
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
inputTokensAbove: 272_000,
|
||||||
|
input: 0.2,
|
||||||
|
output: 0.9,
|
||||||
|
cacheRead: 0.02,
|
||||||
|
cacheWrite: 0.25,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
|
"gpt-5.4": { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
|
||||||
|
"gpt-5.3-codex": { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
|
"gpt-5.4-mini": { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 },
|
||||||
|
|
||||||
|
// Google and xAI
|
||||||
|
"google/gemini-3.6-flash": { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 },
|
||||||
|
"google/gemini-3.5-flash": { input: 1.5, output: 9, cacheRead: 0.15, cacheWrite: 0 },
|
||||||
|
"google/gemini-3.5-flash-lite": {
|
||||||
|
input: 0.3,
|
||||||
|
output: 2.5,
|
||||||
|
cacheRead: 0.03,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"google/gemini-3.1-flash-lite": {
|
||||||
|
input: 0.25,
|
||||||
|
output: 1.5,
|
||||||
|
cacheRead: 0.03,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
"xai/grok-4.5": { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TEMPORARY_PRICING: readonly TemporaryPricing[] = [
|
||||||
|
{
|
||||||
|
models: ["gpt-5.6-terra", "gpt-5.6-luna"],
|
||||||
|
expiresOn: "2026-08-14",
|
||||||
|
description: "50% promotional rates",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
models: ["claude-sonnet-5"],
|
||||||
|
expiresOn: "2026-08-31",
|
||||||
|
description: "introductory pricing",
|
||||||
|
},
|
||||||
|
]
|
||||||
+10
-1
@@ -15,6 +15,7 @@ export interface Usage {
|
|||||||
output: number
|
output: number
|
||||||
cacheRead: number
|
cacheRead: number
|
||||||
cacheWrite: number
|
cacheWrite: number
|
||||||
|
cacheWrite1h?: number
|
||||||
totalTokens: number
|
totalTokens: number
|
||||||
cost: UsageCost
|
cost: UsageCost
|
||||||
}
|
}
|
||||||
@@ -50,13 +51,21 @@ export interface AssistantMessageLike {
|
|||||||
timestamp: number
|
timestamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelCost {
|
export interface ModelCostRates {
|
||||||
input: number
|
input: number
|
||||||
output: number
|
output: number
|
||||||
cacheRead: number
|
cacheRead: number
|
||||||
cacheWrite: number
|
cacheWrite: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelCostTier extends ModelCostRates {
|
||||||
|
inputTokensAbove: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelCost extends ModelCostRates {
|
||||||
|
tiers?: readonly ModelCostTier[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModelLike {
|
export interface ModelLike {
|
||||||
id: string
|
id: string
|
||||||
api: unknown
|
api: unknown
|
||||||
|
|||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"fetchedAt": "2026-08-04T10:12:57.953Z",
|
||||||
|
"source": "https://api.commandcode.ai/provider/v1/models",
|
||||||
|
"modelIds": [
|
||||||
|
"claude-sonnet-5",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
"claude-fable-5",
|
||||||
|
"claude-opus-5",
|
||||||
|
"claude-opus-4-8",
|
||||||
|
"claude-opus-4-7",
|
||||||
|
"claude-haiku-4-5-20251001",
|
||||||
|
"gpt-5.6-sol",
|
||||||
|
"gpt-5.6-terra",
|
||||||
|
"gpt-5.6-luna",
|
||||||
|
"gpt-5.5",
|
||||||
|
"gpt-5.4",
|
||||||
|
"gpt-5.3-codex",
|
||||||
|
"gpt-5.4-mini",
|
||||||
|
"deepseek/deepseek-v4-pro",
|
||||||
|
"deepseek/deepseek-v4-flash",
|
||||||
|
"moonshotai/Kimi-K3",
|
||||||
|
"moonshotai/Kimi-K2.7-Code",
|
||||||
|
"moonshotai/Kimi-K2.7-Code-Highspeed",
|
||||||
|
"moonshotai/Kimi-K2.6",
|
||||||
|
"moonshotai/Kimi-K2.5",
|
||||||
|
"zai-org/GLM-5.2",
|
||||||
|
"zai-org/GLM-5.2-Fast",
|
||||||
|
"zai-org/GLM-5.1",
|
||||||
|
"zai-org/GLM-5",
|
||||||
|
"MiniMaxAI/MiniMax-M3",
|
||||||
|
"MiniMaxAI/MiniMax-M2.7",
|
||||||
|
"MiniMaxAI/MiniMax-M2.5",
|
||||||
|
"xiaomi/mimo-v2.5-pro",
|
||||||
|
"xiaomi/mimo-v2.5",
|
||||||
|
"Qwen/Qwen3.8-Max",
|
||||||
|
"Qwen/Qwen3.7-Max",
|
||||||
|
"Qwen/Qwen3.7-Plus",
|
||||||
|
"Qwen/Qwen3.7-Flash",
|
||||||
|
"Qwen/Qwen3.6-Max-Preview",
|
||||||
|
"Qwen/Qwen3.6-Plus",
|
||||||
|
"stepfun/Step-3.7-Flash",
|
||||||
|
"stepfun/Step-3.5-Flash",
|
||||||
|
"tencent/hy3-paid",
|
||||||
|
"google/gemini-3.6-flash",
|
||||||
|
"google/gemini-3.5-flash",
|
||||||
|
"google/gemini-3.5-flash-lite",
|
||||||
|
"google/gemini-3.1-flash-lite",
|
||||||
|
"sakana/fugu-ultra",
|
||||||
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||||
|
"thinkingmachines/inkling",
|
||||||
|
"thinkingmachines/inkling-small",
|
||||||
|
"poolside/laguna-s-2.1-free",
|
||||||
|
"inclusionai/ling-3.0-flash-free",
|
||||||
|
"meta/muse-spark-1.1",
|
||||||
|
"xai/grok-4.5"
|
||||||
|
]
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"verifiedAt": "2026-08-04",
|
||||||
|
"source": "https://commandcode.ai/docs/resources/pricing-limits",
|
||||||
|
"tierPolicy": "Use request-wide input tiers; the highest threshold exceeded by input plus cache tokens applies to the full request.",
|
||||||
|
"tiers": {
|
||||||
|
"Qwen/Qwen3.7-Plus": [[256000, 1.2, 4.8, 0.24, 1.5]],
|
||||||
|
"Qwen/Qwen3.7-Flash": [
|
||||||
|
[32000, 0.1, 0.4, 0.02, 0.125],
|
||||||
|
[256000, 0.2, 0.8, 0.04, 0.25]
|
||||||
|
],
|
||||||
|
"gpt-5.6-terra": [[272000, 2, 9, 0.2, 2.5]],
|
||||||
|
"gpt-5.6-luna": [[272000, 0.2, 0.9, 0.02, 0.25]]
|
||||||
|
},
|
||||||
|
"costs": {
|
||||||
|
"poolside/laguna-s-2.1-free": [0, 0, 0, 0],
|
||||||
|
"inclusionai/ling-3.0-flash-free": [0, 0, 0, 0],
|
||||||
|
"tencent/hy3-paid": [0.14, 0.58, 0.035, 0],
|
||||||
|
"moonshotai/Kimi-K3": [3, 15, 0.3, 0],
|
||||||
|
"moonshotai/Kimi-K2.7-Code": [0.95, 4, 0.19, 0],
|
||||||
|
"moonshotai/Kimi-K2.7-Code-Highspeed": [1.9, 8, 0.38, 0],
|
||||||
|
"moonshotai/Kimi-K2.6": [0.95, 4, 0.16, 0],
|
||||||
|
"moonshotai/Kimi-K2.5": [0.6, 3, 0.1, 0],
|
||||||
|
"zai-org/GLM-5.2": [1.4, 4.4, 0.26, 0],
|
||||||
|
"zai-org/GLM-5.2-Fast": [3, 10.25, 0.5, 0],
|
||||||
|
"zai-org/GLM-5.1": [1.4, 4.4, 0.26, 0],
|
||||||
|
"zai-org/GLM-5": [1, 3.2, 0.2, 0],
|
||||||
|
"MiniMaxAI/MiniMax-M3": [0.3, 1.2, 0.06, 0],
|
||||||
|
"MiniMaxAI/MiniMax-M2.7": [0.3, 1.2, 0.06, 0],
|
||||||
|
"MiniMaxAI/MiniMax-M2.5": [0.3, 1.2, 0.03, 0],
|
||||||
|
"deepseek/deepseek-v4-pro": [0.435, 0.87, 0.003625, 0],
|
||||||
|
"deepseek/deepseek-v4-flash": [0.14, 0.28, 0.0028, 0],
|
||||||
|
"Qwen/Qwen3.8-Max": [2, 6, 0.25, 2.5],
|
||||||
|
"Qwen/Qwen3.7-Max": [2.5, 7.5, 0.5, 3.13],
|
||||||
|
"Qwen/Qwen3.7-Plus": [0.4, 1.6, 0.08, 0.5],
|
||||||
|
"Qwen/Qwen3.7-Flash": [0.03, 0.13, 0.006, 0.038],
|
||||||
|
"Qwen/Qwen3.6-Max-Preview": [1.3, 7.8, 0.26, 1.63],
|
||||||
|
"Qwen/Qwen3.6-Plus": [0.5, 3, 0.1, 0],
|
||||||
|
"stepfun/Step-3.7-Flash": [0.2, 1.15, 0.04, 0],
|
||||||
|
"stepfun/Step-3.5-Flash": [0.1, 0.3, 0.02, 0],
|
||||||
|
"xiaomi/mimo-v2.5-pro": [0.435, 0.87, 0.0036, 0],
|
||||||
|
"xiaomi/mimo-v2.5": [0.14, 0.28, 0.0028, 0],
|
||||||
|
"nvidia/nemotron-3-ultra-550b-a55b": [0.6, 2.4, 0.12, 0],
|
||||||
|
"sakana/fugu-ultra": [5, 30, 0.5, 0],
|
||||||
|
"thinkingmachines/inkling": [1, 4.05, 0.17, 0],
|
||||||
|
"thinkingmachines/inkling-small": [0.5, 1.2, 0.1, 0],
|
||||||
|
"meta/muse-spark-1.1": [1.25, 4.25, 0.15, 0],
|
||||||
|
"claude-sonnet-5": [2, 10, 0.2, 2.5],
|
||||||
|
"claude-sonnet-4-6": [3, 15, 0.3, 3.75],
|
||||||
|
"claude-fable-5": [10, 50, 1, 12.5],
|
||||||
|
"claude-opus-5": [5, 25, 0.5, 6.25],
|
||||||
|
"claude-opus-4-8": [5, 25, 0.5, 6.25],
|
||||||
|
"claude-opus-4-7": [5, 25, 0.5, 6.25],
|
||||||
|
"claude-haiku-4-5-20251001": [1, 5, 0.1, 1.25],
|
||||||
|
"gpt-5.6-sol": [5, 30, 0.5, 6.25],
|
||||||
|
"gpt-5.6-terra": [1, 6, 0.1, 1.25],
|
||||||
|
"gpt-5.6-luna": [0.1, 0.6, 0.01, 0.125],
|
||||||
|
"gpt-5.5": [5, 30, 0.5, 0],
|
||||||
|
"gpt-5.4": [2.5, 15, 0.25, 0],
|
||||||
|
"gpt-5.3-codex": [2, 8, 0.5, 0],
|
||||||
|
"gpt-5.4-mini": [0.75, 4.5, 0.075, 0],
|
||||||
|
"google/gemini-3.6-flash": [1.5, 7.5, 0.15, 0],
|
||||||
|
"google/gemini-3.5-flash": [1.5, 9, 0.15, 0],
|
||||||
|
"google/gemini-3.5-flash-lite": [0.3, 2.5, 0.03, 0],
|
||||||
|
"google/gemini-3.1-flash-lite": [0.25, 1.5, 0.03, 0],
|
||||||
|
"xai/grok-4.5": [2, 6, 0.5, 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -79,7 +79,7 @@ export function makeModel(overrides: Partial<ModelLike> = {}): ModelLike {
|
|||||||
api: "commandcode-custom",
|
api: "commandcode-custom",
|
||||||
provider: "commandcode",
|
provider: "commandcode",
|
||||||
maxTokens: 384_000,
|
maxTokens: 384_000,
|
||||||
cost: { input: 0.14, output: 0.28, cacheRead: 0.028, cacheWrite: 0 },
|
cost: { input: 0.14, output: 0.28, cacheRead: 0.0028, cacheWrite: 0 },
|
||||||
...overrides,
|
...overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-6
@@ -13,13 +13,17 @@ import { describe, it } from "node:test"
|
|||||||
import { calculateCommandCodeCost } from "../src/cost.ts"
|
import { calculateCommandCodeCost } from "../src/cost.ts"
|
||||||
import type { Usage } from "../src/types.ts"
|
import type { Usage } from "../src/types.ts"
|
||||||
|
|
||||||
interface CostTable {
|
interface CostRates {
|
||||||
input: number
|
input: number
|
||||||
output: number
|
output: number
|
||||||
cacheRead: number
|
cacheRead: number
|
||||||
cacheWrite: number
|
cacheWrite: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CostTable extends CostRates {
|
||||||
|
tiers?: Array<CostRates & { inputTokensAbove: number }>
|
||||||
|
}
|
||||||
|
|
||||||
const COST_FIXTURES: Record<string, CostTable> = {
|
const COST_FIXTURES: Record<string, CostTable> = {
|
||||||
"zero-cost-model": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
"zero-cost-model": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
||||||
@@ -29,7 +33,17 @@ const COST_FIXTURES: Record<string, CostTable> = {
|
|||||||
cacheRead: 0.003625,
|
cacheRead: 0.003625,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0,
|
||||||
},
|
},
|
||||||
"Qwen/Qwen3.7-Max": { input: 1.25, output: 3.75, cacheRead: 0.25, cacheWrite: 1.56 },
|
"Qwen/Qwen3.7-Max": { input: 2.5, output: 7.5, cacheRead: 0.5, cacheWrite: 3.13 },
|
||||||
|
"Qwen/Qwen3.7-Flash": {
|
||||||
|
input: 0.03,
|
||||||
|
output: 0.13,
|
||||||
|
cacheRead: 0.006,
|
||||||
|
cacheWrite: 0.038,
|
||||||
|
tiers: [
|
||||||
|
{ inputTokensAbove: 32_000, input: 0.1, output: 0.4, cacheRead: 0.02, cacheWrite: 0.125 },
|
||||||
|
{ inputTokensAbove: 256_000, input: 0.2, output: 0.8, cacheRead: 0.04, cacheWrite: 0.25 },
|
||||||
|
],
|
||||||
|
},
|
||||||
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
"gpt-5.5": { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +65,14 @@ function commandCodeModel(id: string, cost: CostTable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertClose(actual: number, expected: number) {
|
||||||
|
assert.ok(
|
||||||
|
Math.abs(actual - expected) <=
|
||||||
|
Number.EPSILON * Math.max(1, Math.abs(actual), Math.abs(expected)),
|
||||||
|
`expected ${actual} to be close to ${expected}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function freshUsage(tokens: (typeof USAGE_CASES)[number]): Usage {
|
function freshUsage(tokens: (typeof USAGE_CASES)[number]): Usage {
|
||||||
return {
|
return {
|
||||||
...tokens,
|
...tokens,
|
||||||
@@ -60,10 +82,20 @@ function freshUsage(tokens: (typeof USAGE_CASES)[number]): Usage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function expectedCost(cost: CostTable, tokens: (typeof USAGE_CASES)[number]): Usage["cost"] {
|
function expectedCost(cost: CostTable, tokens: (typeof USAGE_CASES)[number]): Usage["cost"] {
|
||||||
const input = (cost.input / 1_000_000) * tokens.input
|
const inputTokens = tokens.input + tokens.cacheRead + tokens.cacheWrite
|
||||||
const output = (cost.output / 1_000_000) * tokens.output
|
let rates: CostRates = cost
|
||||||
const cacheRead = (cost.cacheRead / 1_000_000) * tokens.cacheRead
|
let matchedThreshold = -1
|
||||||
const cacheWrite = (cost.cacheWrite * tokens.cacheWrite) / 1_000_000
|
for (const tier of cost.tiers ?? []) {
|
||||||
|
if (inputTokens > tier.inputTokensAbove && tier.inputTokensAbove > matchedThreshold) {
|
||||||
|
rates = tier
|
||||||
|
matchedThreshold = tier.inputTokensAbove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = (rates.input / 1_000_000) * tokens.input
|
||||||
|
const output = (rates.output / 1_000_000) * tokens.output
|
||||||
|
const cacheRead = (rates.cacheRead / 1_000_000) * tokens.cacheRead
|
||||||
|
const cacheWrite = (rates.cacheWrite * tokens.cacheWrite) / 1_000_000
|
||||||
return {
|
return {
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -91,6 +123,51 @@ describe("calculateCommandCodeCost()", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("applies the highest request-wide input tier above its threshold", () => {
|
||||||
|
const model = commandCodeModel("Qwen/Qwen3.7-Flash", COST_FIXTURES["Qwen/Qwen3.7-Flash"])
|
||||||
|
|
||||||
|
const atThreshold = freshUsage({
|
||||||
|
input: 32_000,
|
||||||
|
output: 1_000,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
calculateCommandCodeCost(model, atThreshold)
|
||||||
|
assertClose(atThreshold.cost.input, (0.03 * 32_000) / 1_000_000)
|
||||||
|
|
||||||
|
const aboveFirstTier = freshUsage({
|
||||||
|
input: 30_000,
|
||||||
|
output: 1_000,
|
||||||
|
cacheRead: 2_001,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
calculateCommandCodeCost(model, aboveFirstTier)
|
||||||
|
assertClose(aboveFirstTier.cost.input, (0.1 * 30_000) / 1_000_000)
|
||||||
|
assertClose(aboveFirstTier.cost.cacheRead, (0.02 * 2_001) / 1_000_000)
|
||||||
|
|
||||||
|
const aboveHighestTier = freshUsage({
|
||||||
|
input: 100_000,
|
||||||
|
output: 1_000,
|
||||||
|
cacheRead: 156_001,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
calculateCommandCodeCost(model, aboveHighestTier)
|
||||||
|
assertClose(aboveHighestTier.cost.input, (0.2 * 100_000) / 1_000_000)
|
||||||
|
assertClose(aboveHighestTier.cost.output, (0.8 * 1_000) / 1_000_000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prices one-hour cache writes at twice the active input rate", () => {
|
||||||
|
const model = commandCodeModel("claude-sonnet-4-6", COST_FIXTURES["claude-sonnet-4-6"])
|
||||||
|
const usage = freshUsage({ input: 0, output: 0, cacheRead: 0, cacheWrite: 1_000 })
|
||||||
|
usage.cacheWrite1h = 400
|
||||||
|
|
||||||
|
calculateCommandCodeCost(model, usage)
|
||||||
|
|
||||||
|
const expectedShortWrite = (3.75 * 600) / 1_000_000
|
||||||
|
const expectedLongWrite = (3 * 2 * 400) / 1_000_000
|
||||||
|
assertClose(usage.cost.cacheWrite, expectedShortWrite + expectedLongWrite)
|
||||||
|
})
|
||||||
|
|
||||||
it("writes the total as the sum of all cost components", () => {
|
it("writes the total as the sum of all cost components", () => {
|
||||||
const model = commandCodeModel("claude-sonnet-4-6", COST_FIXTURES["claude-sonnet-4-6"])
|
const model = commandCodeModel("claude-sonnet-4-6", COST_FIXTURES["claude-sonnet-4-6"])
|
||||||
const usage = freshUsage({ input: 1_000, output: 500, cacheRead: 10_000, cacheWrite: 2_000 })
|
const usage = freshUsage({ input: 1_000, output: 500, cacheRead: 10_000, cacheWrite: 2_000 })
|
||||||
|
|||||||
+160
-73
@@ -1,90 +1,177 @@
|
|||||||
import assert from "node:assert/strict"
|
import assert from "node:assert/strict"
|
||||||
|
import { readFile } from "node:fs/promises"
|
||||||
import { describe, it } from "node:test"
|
import { describe, it } from "node:test"
|
||||||
|
|
||||||
// MODEL_COSTS is a module-level const in index.ts. We verify the pricing
|
import {
|
||||||
// overlay by importing the map through a dedicated re-export so tests don't
|
MODEL_COSTS,
|
||||||
// need to spin up the full extension.
|
PRICING_LAST_VERIFIED,
|
||||||
//
|
PRICING_SOURCE_URL,
|
||||||
// To keep the test self-contained without importing the full extension (which
|
TEMPORARY_PRICING,
|
||||||
// requires ExtensionAPI), we read the source and extract the constant at
|
} from "../src/pricing.ts"
|
||||||
// runtime. A cleaner approach would be a dedicated src/pricing.ts module,
|
|
||||||
// but for now we verify the known cost entries directly.
|
|
||||||
|
|
||||||
import { readFileSync } from "node:fs"
|
interface ModelCatalogSnapshot {
|
||||||
import { resolve, dirname } from "node:path"
|
fetchedAt: string
|
||||||
import { fileURLToPath } from "node:url"
|
source: string
|
||||||
|
modelIds: string[]
|
||||||
|
}
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
interface PricingSnapshot {
|
||||||
const indexSource = readFileSync(resolve(__dirname, "..", "index.ts"), "utf-8")
|
verifiedAt: string
|
||||||
|
source: string
|
||||||
|
tierPolicy: string
|
||||||
|
tiers: Record<string, [number, number, number, number, number][]>
|
||||||
|
costs: Record<string, [number, number, number, number]>
|
||||||
|
}
|
||||||
|
|
||||||
// Extract MODEL_COSTS object from index.ts source using a simple parse.
|
const fixtureUrl = new URL("./fixtures/commandcode-model-ids.json", import.meta.url)
|
||||||
// The map is written as a Record<string, {input:number,output:number,...}>
|
const fixture = JSON.parse(await readFile(fixtureUrl, "utf-8")) as ModelCatalogSnapshot
|
||||||
// so we eval it in a sandboxed context.
|
const pricingFixtureUrl = new URL("./fixtures/commandcode-pricing.json", import.meta.url)
|
||||||
const match = indexSource.match(
|
const pricingFixture = JSON.parse(await readFile(pricingFixtureUrl, "utf-8")) as PricingSnapshot
|
||||||
/const MODEL_COSTS:\s*Record<string,\s*CommandCodeModelCost>\s*=\s*\{([\s\S]*?)\n\}/,
|
const freeModels = new Set(["poolside/laguna-s-2.1-free", "inclusionai/ling-3.0-flash-free"])
|
||||||
|
|
||||||
|
function assertCost(
|
||||||
|
modelId: string,
|
||||||
|
expected: { input: number; output: number; cacheRead: number; cacheWrite: number },
|
||||||
|
) {
|
||||||
|
const cost = MODEL_COSTS[modelId]
|
||||||
|
assert.ok(cost, `${modelId} should have pricing`)
|
||||||
|
assert.deepEqual(
|
||||||
|
{
|
||||||
|
input: cost.input,
|
||||||
|
output: cost.output,
|
||||||
|
cacheRead: cost.cacheRead,
|
||||||
|
cacheWrite: cost.cacheWrite,
|
||||||
|
},
|
||||||
|
expected,
|
||||||
|
`${modelId} base pricing should match the source`,
|
||||||
)
|
)
|
||||||
assert.ok(match, "MODEL_COSTS constant should exist in index.ts")
|
|
||||||
|
|
||||||
// Parse the cost entries from the extracted block.
|
|
||||||
const costBlock = match[1]
|
|
||||||
const entries: Record<string, { input: number; output: number }> = {}
|
|
||||||
for (const line of costBlock.split("\n")) {
|
|
||||||
const trimmed = line.trim()
|
|
||||||
if (!trimmed || trimmed.startsWith("//")) continue
|
|
||||||
const entryMatch = trimmed.match(/^"([^"]+)":\s*\{\s*input:\s*([\d.]+),\s*output:\s*([\d.]+)/)
|
|
||||||
if (entryMatch) {
|
|
||||||
entries[entryMatch[1]] = {
|
|
||||||
input: Number(entryMatch[2]),
|
|
||||||
output: Number(entryMatch[3]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("MODEL_COSTS pricing overlay", () => {
|
describe("MODEL_COSTS pricing overlay", () => {
|
||||||
it("covers known Command Code models with non-zero pricing", () => {
|
it("covers the current Command Code model catalog snapshot", () => {
|
||||||
const knownModels = [
|
assert.equal(fixture.source, "https://api.commandcode.ai/provider/v1/models")
|
||||||
"deepseek/deepseek-v4-flash",
|
assert.match(fixture.fetchedAt, /^2026-08-04T/)
|
||||||
"deepseek/deepseek-v4-pro",
|
|
||||||
"claude-sonnet-4-6",
|
|
||||||
"claude-opus-4-7",
|
|
||||||
"Qwen/Qwen3.7-Max",
|
|
||||||
"gpt-5.5",
|
|
||||||
"stepfun/Step-3.5-Flash",
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const id of knownModels) {
|
const catalogIds = [...fixture.modelIds].sort()
|
||||||
const cost = entries[id]
|
const pricedIds = Object.keys(MODEL_COSTS).sort()
|
||||||
assert.ok(cost, `MODEL_COSTS should include "${id}"`)
|
assert.deepEqual(pricedIds, catalogIds)
|
||||||
assert.ok(cost.input > 0, `"${id}" input cost should be > 0`)
|
})
|
||||||
assert.ok(cost.output > 0, `"${id}" output cost should be > 0`)
|
|
||||||
|
it("matches the verified official pricing snapshot", () => {
|
||||||
|
assert.equal(pricingFixture.verifiedAt, PRICING_LAST_VERIFIED)
|
||||||
|
assert.equal(pricingFixture.source, PRICING_SOURCE_URL)
|
||||||
|
assert.match(pricingFixture.tierPolicy, /request-wide input tiers/)
|
||||||
|
|
||||||
|
const expected = Object.fromEntries(
|
||||||
|
Object.entries(pricingFixture.costs).map(
|
||||||
|
([modelId, [input, output, cacheRead, cacheWrite]]) => [
|
||||||
|
modelId,
|
||||||
|
{
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
cacheRead,
|
||||||
|
cacheWrite,
|
||||||
|
...(pricingFixture.tiers[modelId]
|
||||||
|
? {
|
||||||
|
tiers: pricingFixture.tiers[modelId].map(
|
||||||
|
([inputTokensAbove, tierInput, tierOutput, tierCacheRead, tierCacheWrite]) => ({
|
||||||
|
inputTokensAbove,
|
||||||
|
input: tierInput,
|
||||||
|
output: tierOutput,
|
||||||
|
cacheRead: tierCacheRead,
|
||||||
|
cacheWrite: tierCacheWrite,
|
||||||
|
}),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
: {}),
|
||||||
|
},
|
||||||
it("includes promotional pricing notes in comments", () => {
|
],
|
||||||
// The DeepSeek V4 Pro 4× deal and Qwen 3.7 Max 2× deal should be
|
|
||||||
// documented in the source comments.
|
|
||||||
assert.ok(
|
|
||||||
costBlock.includes("4× usage deal") || costBlock.includes("75% off"),
|
|
||||||
"DeepSeek V4 Pro promotional pricing should be documented",
|
|
||||||
)
|
|
||||||
assert.ok(
|
|
||||||
costBlock.includes("2× usage deal") || costBlock.includes("50% off"),
|
|
||||||
"Qwen 3.7 Max promotional pricing should be documented",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("has cache pricing for models that support it", () => {
|
|
||||||
// Claude models should have non-zero cacheRead and cacheWrite costs.
|
|
||||||
const claudeModels = ["claude-sonnet-4-6", "claude-opus-4-7"]
|
|
||||||
for (const id of claudeModels) {
|
|
||||||
const fullEntryMatch = costBlock.match(
|
|
||||||
new RegExp(
|
|
||||||
`"${id.replace(/\//g, "\\\\")}":\\s*\\{[^}]+cacheRead:\\s*([\\d.]+)[^}]+cacheWrite:\\s*([\\d.]+)`,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert.ok(fullEntryMatch, `"${id}" should have cacheRead and cacheWrite fields`)
|
assert.deepEqual(MODEL_COSTS, expected)
|
||||||
assert.ok(Number(fullEntryMatch[1]) > 0, `"${id}" cacheRead should be > 0`)
|
})
|
||||||
assert.ok(Number(fullEntryMatch[2]) > 0, `"${id}" cacheWrite should be > 0`)
|
|
||||||
|
it("uses non-zero prices except for models documented as free", () => {
|
||||||
|
for (const [modelId, cost] of Object.entries(MODEL_COSTS)) {
|
||||||
|
assert.ok(cost.input >= 0, `${modelId} input cost should be non-negative`)
|
||||||
|
assert.ok(cost.output >= 0, `${modelId} output cost should be non-negative`)
|
||||||
|
assert.ok(cost.cacheRead >= 0, `${modelId} cache-read cost should be non-negative`)
|
||||||
|
assert.ok(cost.cacheWrite >= 0, `${modelId} cache-write cost should be non-negative`)
|
||||||
|
|
||||||
|
const allZero = Object.values(cost).every((value) => value === 0)
|
||||||
|
assert.equal(
|
||||||
|
allZero,
|
||||||
|
freeModels.has(modelId),
|
||||||
|
`${modelId} free-model status should be explicit`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("matches corrected official rates", () => {
|
||||||
|
assertCost("deepseek/deepseek-v4-flash", {
|
||||||
|
input: 0.14,
|
||||||
|
output: 0.28,
|
||||||
|
cacheRead: 0.0028,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
assertCost("Qwen/Qwen3.7-Max", {
|
||||||
|
input: 2.5,
|
||||||
|
output: 7.5,
|
||||||
|
cacheRead: 0.5,
|
||||||
|
cacheWrite: 3.13,
|
||||||
|
})
|
||||||
|
assertCost("xiaomi/mimo-v2.5-pro", {
|
||||||
|
input: 0.435,
|
||||||
|
output: 0.87,
|
||||||
|
cacheRead: 0.0036,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
assertCost("MiniMaxAI/MiniMax-M2.5", {
|
||||||
|
input: 0.3,
|
||||||
|
output: 1.2,
|
||||||
|
cacheRead: 0.03,
|
||||||
|
cacheWrite: 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("uses the documented base rates for context-dependent models", () => {
|
||||||
|
assertCost("Qwen/Qwen3.7-Plus", {
|
||||||
|
input: 0.4,
|
||||||
|
output: 1.6,
|
||||||
|
cacheRead: 0.08,
|
||||||
|
cacheWrite: 0.5,
|
||||||
|
})
|
||||||
|
assertCost("Qwen/Qwen3.7-Flash", {
|
||||||
|
input: 0.03,
|
||||||
|
output: 0.13,
|
||||||
|
cacheRead: 0.006,
|
||||||
|
cacheWrite: 0.038,
|
||||||
|
})
|
||||||
|
assertCost("gpt-5.6-terra", {
|
||||||
|
input: 1,
|
||||||
|
output: 6,
|
||||||
|
cacheRead: 0.1,
|
||||||
|
cacheWrite: 1.25,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("tracks pricing provenance", () => {
|
||||||
|
assert.equal(PRICING_SOURCE_URL, "https://commandcode.ai/docs/resources/pricing-limits")
|
||||||
|
assert.equal(PRICING_LAST_VERIFIED, "2026-08-04")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails once temporary pricing needs review", () => {
|
||||||
|
const today = new Date().toISOString().slice(0, 10)
|
||||||
|
for (const pricing of TEMPORARY_PRICING) {
|
||||||
|
assert.match(pricing.expiresOn, /^\d{4}-\d{2}-\d{2}$/)
|
||||||
|
assert.ok(pricing.models.length > 0)
|
||||||
|
assert.ok(
|
||||||
|
pricing.expiresOn >= today,
|
||||||
|
`${pricing.description} for ${pricing.models.join(", ")} expired on ${pricing.expiresOn}; refresh MODEL_COSTS`,
|
||||||
|
)
|
||||||
|
for (const modelId of pricing.models) {
|
||||||
|
assert.ok(MODEL_COSTS[modelId], `${modelId} should have a temporary price entry`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user