Files
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

79 lines
2.9 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.
// Собранный сервер отвечает на initialize и tools/list, а в stdout не
// пишет ничего, кроме JSON-RPC. В сеть не ходит: tools/list её не требует.
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL("..", import.meta.url));
test("dist/index.js: stdout — только валидный JSON-RPC, шесть read-only инструментов", { timeout: 60_000 }, async () => {
execFileSync(process.execPath, ["node_modules/typescript/bin/tsc", "-p", "tsconfig.build.json"], {
cwd: root,
stdio: "inherit",
});
const child = spawn(process.execPath, ["dist/index.js"], {
cwd: root,
env: { PATH: process.env.PATH ?? "", OPENRUSROUTER_API_KEY: "test", OPENRUSROUTER_BASE_URL: "http://localhost:1" },
stdio: ["pipe", "pipe", "pipe"],
});
const lines: string[] = [];
let buf = "";
const waiters = new Map<number, (msg: Record<string, unknown>) => void>();
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
buf += chunk;
let i: number;
while ((i = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, i);
buf = buf.slice(i + 1);
lines.push(line);
try {
const msg = JSON.parse(line) as Record<string, unknown>;
if (typeof msg.id === "number") waiters.get(msg.id)?.(msg);
} catch {
// невалидная строка будет поймана проверкой ниже
}
}
});
child.stderr.resume();
const send = (msg: object) => child.stdin.write(`${JSON.stringify(msg)}\n`);
const request = (id: number, method: string, params: object) =>
new Promise<Record<string, unknown>>((resolve) => {
waiters.set(id, resolve);
send({ jsonrpc: "2.0", id, method, params });
});
try {
const init = await request(1, "initialize", {
protocolVersion: "2025-06-18",
capabilities: {},
clientInfo: { name: "test", version: "0" },
});
assert.equal((init.result as { serverInfo: { name: string } }).serverInfo.name, "openrusrouter");
send({ jsonrpc: "2.0", method: "notifications/initialized" });
const list = await request(2, "tools/list", {});
const tools = (list.result as { tools: { name: string; annotations?: Record<string, unknown> }[] }).tools;
assert.deepEqual(
tools.map((t) => t.name).sort(),
["estimate_cost", "get_balance", "get_usage", "list_api_keys", "list_models", "pick_model"],
);
for (const t of tools) {
assert.equal(t.annotations?.readOnlyHint, true, t.name);
assert.equal(t.annotations?.destructiveHint, false, t.name);
}
} finally {
child.kill();
}
assert.ok(lines.length >= 2);
for (const line of lines) {
assert.doesNotThrow(() => JSON.parse(line), `не JSON в stdout: ${line.slice(0, 100)}`);
}
});