fix(models): refresh model pricing

This commit is contained in:
Patrick Wozniak
2026-08-04 13:46:26 +02:00
parent 690c640d5b
commit 947915df55
10 changed files with 619 additions and 127 deletions
+1
View File
@@ -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
+1 -45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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`)
}
} }
}) })
}) })