Добавь 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:
1 parent
ade6eb82df
commit
d7d02267e2
18 files changed
+1935
No files matched your search
@@ -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);
|
||||
});
|
||||
Reference in new issue
Block a user