diff --git a/lib/estimate.js b/lib/estimate.js index 14de8b9..3559d40 100644 --- a/lib/estimate.js +++ b/lib/estimate.js @@ -90,6 +90,40 @@ const SPEECH_RATE = { const SPEECH_DEFAULT_RATE = 0.05; // elevenlabs/flash-v2.5 (the MCP default) const SOUND_EFFECT_COST = 0.0525; +// Chat models billed $0 — mirrors @blockrun/mcp FREE_CHAT_MODELS +// (src/utils/constants.ts), refreshed 2026-10-20. Membership by id, not +// vendor: cohere/ and poolside/ have $0 models, and not every nvidia/ id is +// guaranteed free. Sources: GET /v1/models billing_mode "free" and the account +// price sheet (blockrun.ai/api/pricing) at $0. +const FREE_CHAT_MODELS = new Set([ + "nvidia/gpt-oss-20b", "nvidia/nemotron-3-super-120b", "nvidia/llama-3.2-11b-vision", + "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", "cohere/north-mini-code", "poolside/laguna-xs-2.1", + "nvidia/gpt-oss-120b", "nvidia/nemotron-3-ultra-550b", "nvidia/step-3.7-flash", "nvidia/mistral-nemotron", + "nvidia/nemotron-nano-12b-v2-vl", "nvidia/nemotron-nano-9b-v2", "nvidia/nemotron-3.5-lightning", + "nvidia/muse-glimmer-30b", "nvidia/gemma-4-31b", "nvidia/nemotron-3-super-120b-a12b-free", + "nvidia/qwen3.5-397b-a17b", "nvidia/nemotron-ultra-253b", "nvidia/nemotron-super-49b", "nvidia/deepseek-v3.2", + "nvidia/nemotron-3-nano-omni-nim", + "nvidia/mistral-large-3-675b", "nvidia/qwen3-coder-480b", "nvidia/devstral-2-123b", "nvidia/glm-4.7", + "nvidia/llama-4-maverick", "nvidia/qwen3-next-80b-a3b-instruct", "nvidia/qwen3.5-122b-a10b", + "nvidia/seed-oss-36b", "nvidia/mistral-small-4-119b", +]); +// nvidia/deepseek-v4-pro and nvidia/deepseek-v4-flash are NOT here: the +// gateways retired both (unknown-model 400; blockrun#848 and its follow-up). +// +// Bare spellings the gateway also accepts. A free id whose bare name belongs +// to a PAID model must be listed here and excluded, so the paid model wins +// the spelling (MCP FREE_SHADOWED_BY_PAID). Empty since the nvidia DeepSeek +// mirror was retired; bare "deepseek-v4-pro" is the paid +// deepseek/deepseek-v4-pro ($1.32/$3.96) and is not in the free set at all. +const FREE_SHADOWED_BY_PAID = new Set([]); +const FREE_CHAT_BARE = new Set( + [...FREE_CHAT_MODELS].filter((id) => !FREE_SHADOWED_BY_PAID.has(id)).map((id) => id.slice(id.indexOf("/") + 1)), +); +function isFreeChatModel(model) { + const id = String(model || "").trim(); + return FREE_CHAT_MODELS.has(id) || (!id.includes("/") && FREE_CHAT_BARE.has(id)); +} + // Whether `size` is in `model`'s large tier (see IMAGE_LARGE_THRESHOLD / // IMAGE_LARGE_MIN_DIM). Unparseable sizes price at the base. function isLargerThanBase(model, size) { @@ -147,7 +181,9 @@ function estimate(toolName, input = {}) { return { usd: paid ? 0.001 : 0, label: `price · ${cat || "crypto"}${paid ? "" : " (free)"}` }; } case "chat": { - const free = input.mode === "free" || /^nvidia\//.test(input.model || ""); + // An explicit model wins over mode, as in the MCP: mode is ignored when + // model is set. + const free = input.model ? isFreeChatModel(input.model) : input.mode === "free"; return { usd: free ? 0 : 0.005, label: `chat${free ? " (free tier)" : ""}` }; } // Wallet + models are always free; data tools (if a fuller profile is used)