Добавь 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,50 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import type { ApiKeyEntry, UsageResponse } from "../src/client.js";
|
||||
import { FAILED_NOTE, summarizeUsage, usageWindow } from "../src/tools/billing.js";
|
||||
import { KEYS_TOOL_DESCRIPTION, presentKeys } from "../src/tools/keys.js";
|
||||
import { fixture } from "./helpers.js";
|
||||
|
||||
test("usageWindow: to = сейчас, from = сейчас − days, без миллисекунд", () => {
|
||||
const w = usageWindow(7, new Date("2026-10-24T03:39:57.123Z"));
|
||||
assert.deepEqual(w, { from: "2026-10-17T03:39:57Z", to: "2026-10-24T03:39:57Z" });
|
||||
});
|
||||
|
||||
test("summarizeUsage: total по всем строкам до обрезки, decimal-сложение", () => {
|
||||
const resp = fixture<UsageResponse>("api-usage");
|
||||
const s = summarizeUsage(resp, 2);
|
||||
// 0.8124417 + 0.18412076 + 0.24310558 + 0.05120934 + 0.00371284 = 1.29459022
|
||||
assert.equal(s.total_rub, "1.29459022");
|
||||
assert.equal((s.items as unknown[]).length, 2);
|
||||
assert.equal(s.truncated, true);
|
||||
assert.equal(s.failed_note, null);
|
||||
const failed = summarizeUsage({ ...resp, failed_events: 3 }, 50);
|
||||
assert.equal(failed.failed_note, FAILED_NOTE);
|
||||
assert.equal(failed.truncated, false);
|
||||
});
|
||||
|
||||
test("presentKeys: ключ не выводится, текущий отмечен по префиксу, отозванные скрыты", () => {
|
||||
const { keys } = fixture<{ keys: ApiKeyEntry[] }>("api-keys");
|
||||
const current = "orr_live_Gh5iJkSECRETSECRETSECRETSECRET1";
|
||||
const withRevoked = [...keys, { ...(keys[0] as ApiKeyEntry), id: "rev", revoked_at: "2026-10-01T00:00:00Z" }];
|
||||
const out = presentKeys(withRevoked, current, false);
|
||||
assert.equal(out.length, 3);
|
||||
assert.deepEqual(out.map((k) => k.is_current_key), [false, true, false]);
|
||||
assert.ok(!JSON.stringify(out).includes(current));
|
||||
assert.equal(out[0]?.all_models_allowed, true);
|
||||
assert.equal(out[0]?.allowed_models, null);
|
||||
assert.deepEqual(out[1]?.allowed_models, ["openai/gpt-4o-mini"]);
|
||||
assert.equal(out[1]?.all_models_allowed, false);
|
||||
assert.equal(out[0]?.spend_used_rub, "12.5784961");
|
||||
assert.equal(out[0]?.key_prefix, "orr_live_Ab3dEf");
|
||||
assert.equal(presentKeys(withRevoked, current, true).length, 4);
|
||||
});
|
||||
|
||||
test("описание list_api_keys дословно говорит о запрете управления ключами", () => {
|
||||
assert.ok(
|
||||
KEYS_TOOL_DESCRIPTION.includes(
|
||||
"Создать, отозвать или изменить ключ через MCP нельзя — такие действия требуют браузерной сессии на openrusrouter.ru. " +
|
||||
"Это сознательное ограничение безопасности: ключ, которым пользуется MCP, не должен уметь управлять другими ключами или собой",
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user