Files
openrusrouter-mcp/test/estimate-cost.test.ts
vasyanskandClaude Opus 5.5 d7d02267e2 Добавь HTTP-клиент, каталог моделей и шесть read-only инструментов MCP
Клиент — замороженный объект с семью методами GET, без универсального
запроса и тела; денежные поля проверяются на строковый тип (fail-closed).
Каталог джойнит /api/models и /v1/models: модальность и рубли из первого,
USD и доступность ключу из второго. Инструменты: list_models, pick_model,
estimate_cost, get_balance, get_usage, list_api_keys. Точка входа stdio
уводит console.log/info в stderr первым импортом.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 10:59:06 +07:00

67 lines
3.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from "node:test";
import assert from "node:assert/strict";
import { estimateCost } from "../src/tools/models.js";
import { byId, fixtureCatalog } from "./helpers.js";
const items = fixtureCatalog();
test("image, transcription, speech — явный отказ, а не ноль", () => {
for (const id of [
"bytedance-seed/seedream-4.5",
"black-forest-labs/flux-3-image",
"openai/whisper-1",
"elevenlabs/eleven-flash-v2",
]) {
const r = estimateCost(byId(items, id), { prompt_tokens: 1000, completion_tokens: 0 });
assert.equal(r.ok, false, id);
if (!r.ok) assert.match(r.error, /нет цены за токен — тарификация по факту, оценка невозможна/);
}
});
test("text-модель: рубли без кэша, USD с ценой кэш-чтения", () => {
const ds = byId(items, "deepseek/deepseek-v4-flash");
// ₽: 1_000_000 × 10.38881086 / 1e6 = 10.38881086
// 500_000 × 20.77762173 / 1e6 = 10.388810865
// итого 20.777621725 → 20.78
// $: (1_000_000 − 400_000) × 0.0000001289925 = 0.0773955
// 400_000 × 0.0000000257985 = 0.0103194
// 500_000 × 0.000000257985 = 0.1289925
// итого 0.2167074
const r = estimateCost(ds, { prompt_tokens: 1_000_000, completion_tokens: 500_000, cached_tokens: 400_000 });
assert.ok(r.ok);
assert.equal(r.cost_rub, "20.777621725");
assert.equal(r.cost_rub_rounded, "20.78");
assert.equal(r.cost_usd, "0.2167074");
assert.match(r.basis, /без учёта скидки за кэш/);
assert.match(r.basis, /цене кэш-чтения/);
const noCache = estimateCost(ds, { prompt_tokens: 1_000_000, completion_tokens: 500_000 });
assert.ok(noCache.ok);
// $: 0.1289925 + 0.1289925 = 0.257985
assert.equal(noCache.cost_usd, "0.257985");
assert.equal(noCache.cost_rub, "20.777621725");
});
test("модель без объявленной цены кэша считает кэш по полной цене и говорит об этом", () => {
const qwen = byId(items, "qwen/qwen3-coder-30b-a3b-instruct");
// $: 1000 × 0.000000099225 = 0.000099225 (кэш по полной цене)
const r = estimateCost(qwen, { prompt_tokens: 1000, cached_tokens: 500 });
assert.ok(r.ok);
assert.equal(r.cost_usd, "0.000099225");
assert.match(r.basis, /не объявляет цену кэш-чтения/);
});
test("decision: выход не тарифицируется", () => {
const jev = byId(items, "typesafe/jev-1.13.0");
// 2_000_000 × 4.79483578 / 1e6 = 9.58967156; выход бесплатен.
const r = estimateCost(jev, { prompt_tokens: 2_000_000, completion_tokens: 10 });
assert.ok(r.ok);
assert.equal(r.cost_rub, "9.58967156");
assert.match(r.basis, /выход decision-модели не тарифицируется/);
});
test("кэш больше промпта — ошибка", () => {
const r = estimateCost(byId(items, "deepseek/deepseek-v4-flash"), { prompt_tokens: 10, cached_tokens: 11 });
assert.equal(r.ok, false);
});