Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

87 lines
2.2 KiB
TypeScript

/**
* Input validation for AI chat endpoints.
* Prevents abuse via oversized payloads and excessive message histories.
*/
interface InputLimits {
/** Maximum number of messages allowed (0 = unlimited) */
maxMessages: number;
/** Maximum total character count of the serialized messages array */
maxTotalChars: number;
/** Maximum serialized (JSON.stringify) character count for a single message */
maxSingleMessageChars: number;
}
const GENERAL_CHAT_LIMITS: InputLimits = {
maxMessages: 20,
maxTotalChars: 96_000, // ~24k tokens
maxSingleMessageChars: 12_000, // ~3k tokens
};
const DOC_CHAT_LIMITS: InputLimits = {
maxMessages: 0, // no message count limit; pruneMessages handles windowing
maxTotalChars: 480_000, // ~120k tokens
maxSingleMessageChars: 24_000, // ~6k tokens
};
function measureMessageChars(messages: unknown[]): {
totalChars: number;
maxChars: number;
} {
let totalChars = 0;
let maxChars = 0;
for (const msg of messages) {
const len = JSON.stringify(msg).length;
totalChars += len;
if (len > maxChars) maxChars = len;
}
return { totalChars, maxChars };
}
function validateWithLimits(
messages: unknown,
limits: InputLimits,
): string | null {
if (!Array.isArray(messages)) {
return "Invalid messages format";
}
if (messages.length === 0) {
return "No messages provided";
}
if (limits.maxMessages > 0 && messages.length > limits.maxMessages) {
return `Too many messages (max ${limits.maxMessages})`;
}
const { totalChars, maxChars } = measureMessageChars(messages);
if (totalChars > limits.maxTotalChars) {
return "Input too long";
}
if (maxChars > limits.maxSingleMessageChars) {
return "Single message too long";
}
return null;
}
export function validateGeneralChatInput(messages: unknown): Response | null {
const error = validateWithLimits(messages, GENERAL_CHAT_LIMITS);
if (error) {
return new Response(error, { status: 400 });
}
return null;
}
export function validateDocChatInput(messages: unknown): Response | null {
const error = validateWithLimits(messages, DOC_CHAT_LIMITS);
if (error) {
return new Response(error, { status: 400 });
}
return null;
}