Добавь 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>
This commit is contained in:
vasyanskandClaude Opus 5.5 committed 2026-10-24 10:59:06 +07:00
1 parent ade6eb82df
commit d7d02267e2
18 files changed
+1935

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
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);
});