import {
isModelId,
type ChatRequest,
type MessageRole,
type ModelId,
} from "@/lib/chat/types";
import {
MAX_CHAT_MESSAGE_CHARACTERS,
MAX_CHAT_REQUEST_CHARACTERS,
MAX_CHAT_REQUEST_MESSAGES,
} from "@/lib/chat/request-limits";
const roles = new Set<MessageRole>(["user", "assistant"]);
export class ChatValidationError extends Error {
readonly status = 400;
constructor() {
super("Некорректный запрос");
this.name = "ChatValidationError";
}
}
export function validateChatRequest(value: unknown): ChatRequest {
if (!isRecord(value) || !isModelId(value.model) || !Array.isArray(value.messages)) {
throw new ChatValidationError();
}
if (
value.messages.length === 0 ||
value.messages.length > MAX_CHAT_REQUEST_MESSAGES
) {
throw new ChatValidationError();
}
let totalCharacters = 0;
const messages = Array.from(value.messages, (message) => {
if (
!isRecord(message) ||
!isMessageRole(message.role) ||
typeof message.content !== "string" ||
message.content.trim().length === 0 ||
message.content.length > MAX_CHAT_MESSAGE_CHARACTERS
) {
throw new ChatValidationError();
}
totalCharacters += message.content.length;
if (totalCharacters > MAX_CHAT_REQUEST_CHARACTERS) {
throw new ChatValidationError();
}
return { role: message.role, content: message.content };
});
return { model: value.model, messages };
}
export type PersistentChatRequest = {
conversationId?: string;
model: ModelId;
content?: string;
retry?: boolean;
attachmentIds?: string[];
};
export function validatePersistentChatRequest(value: unknown): PersistentChatRequest {
if (!isRecord(value) || !isModelId(value.model)) {
throw new ChatValidationError();
}
const conversationId = value.conversationId;
const content = value.content;
const retry = value.retry;
const attachmentIds = value.attachmentIds;
if (
(conversationId !== undefined && (typeof conversationId !== "string" || !conversationId)) ||
(content !== undefined &&
(typeof content !== "string" ||
!content.trim() ||
content.length > MAX_CHAT_MESSAGE_CHARACTERS)) ||
(retry !== undefined && typeof retry !== "boolean") ||
(attachmentIds !== undefined && (!Array.isArray(attachmentIds) || attachmentIds.length > 10 || attachmentIds.some((id) => typeof id !== "string" || !id) || new Set(attachmentIds).size !== attachmentIds.length)) ||
(retry === true && content !== undefined) ||
(retry !== true && content === undefined && (!attachmentIds || attachmentIds.length === 0))
) {
throw new ChatValidationError();
}
return { conversationId, model: value.model, content, retry, attachmentIds };
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isMessageRole(value: unknown): value is MessageRole {
return typeof value === "string" && roles.has(value as MessageRole);
}