package.json, tsconfig, eslint, генерация типов из OpenAPI-спеки, модули config/redact/errors/money/sanitize/cache с тестами и обезличенные фикстуры живых ответов API openrusrouter.ru. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.7 KiB
TypeScript
48 lines
1.7 KiB
TypeScript
import { redactError } from "./redact.js";
|
|
|
|
/** Неуспешный HTTP-ответ API площадки. */
|
|
export class ApiError extends Error {
|
|
override name = "ApiError";
|
|
readonly status: number;
|
|
readonly code?: string;
|
|
readonly path: string;
|
|
|
|
constructor(status: number, path: string, message?: string, code?: string) {
|
|
super(message ?? `HTTP ${status}`);
|
|
this.status = status;
|
|
this.path = path;
|
|
if (code !== undefined) {
|
|
this.code = code;
|
|
}
|
|
}
|
|
}
|
|
|
|
export interface ToolErrorResult {
|
|
[key: string]: unknown;
|
|
isError: true;
|
|
content: [{ type: "text"; text: string }];
|
|
}
|
|
|
|
function textForStatus(status: number | undefined, fallback: string): string {
|
|
if (status === 401) return "ключ недействителен или отозван";
|
|
if (status === 403) return "ключу запрещено выполнять это действие";
|
|
if (status === 429) return "превышен лимит частоты запросов, повторите позже";
|
|
if (status !== undefined && status >= 500 && status <= 599) {
|
|
return "сервис openrusrouter.ru временно недоступен";
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
/** Ошибку любого вида превращает в результат инструмента MCP; текст всегда проходит redactError. */
|
|
export function toToolErrorResult(err: unknown): ToolErrorResult {
|
|
const r = redactError(err);
|
|
let text = textForStatus(r.status, r.message);
|
|
const details = [r.status !== undefined ? `HTTP ${r.status}` : "", r.code ?? "", r.path ?? ""]
|
|
.filter((s) => s !== "")
|
|
.join(", ");
|
|
if (details !== "") {
|
|
text += ` (${details})`;
|
|
}
|
|
return { isError: true, content: [{ type: "text", text }] };
|
|
}
|