grok2api / frontend /src /features /settings /settings-model.ts
fromozuzhouzzz
Deploy grok2api v3.0.11 to HF Spaces
f1dd159
Raw
History Blame Contribute Delete
17.6 kB
import { z } from "zod";
import type { SettingsConfigDTO } from "@/features/settings/settings-api";
export type DurationUnit = "s" | "m" | "h" | "d";
export type DurationValue = { value: number; unit: DurationUnit };
export type ByteSizeUnit = "MiB" | "GiB";
export type ByteSizeValue = { value: number; unit: ByteSizeUnit };
export const MAX_ROUTING_ATTEMPTS = 65535;
export const UNLIMITED_ROUTING_ATTEMPTS = -1;
const durationSchema = z.object({ value: z.number().positive(), unit: z.enum(["s", "m", "h", "d"]) });
const positiveInteger = z.number().int().positive();
const byteSizeSchema = z.object({ value: z.number().positive(), unit: z.enum(["MiB", "GiB"]) });
const routingTTLDuration = durationSchema.refine((value) => durationSeconds(value) <= 30 * 86_400);
const routingCooldownDuration = durationSchema.refine((value) => durationSeconds(value) <= 86_400);
const routingCapacityWaitDuration = durationSchema.refine((value) => durationSeconds(value) <= 30);
const auditFlushDuration = durationSchema.refine((value) => {
const seconds = durationSeconds(value);
return seconds >= 0.01 && seconds <= 60;
});
const consoleChatDuration = durationSchema.refine((value) => {
const seconds = durationSeconds(value);
return seconds >= 5 && seconds <= 30 * 60;
});
const buildResponseHeaderDuration = durationSchema.refine((value) => {
const seconds = durationSeconds(value);
return seconds >= 30 && seconds <= 30 * 60;
});
const forbiddenCodePattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
function parseForbiddenCodes(value: string): string[] {
const seen = new Set<string>();
const result: string[] = [];
for (const item of value.split(/[\n,]/)) {
const code = item.trim().toLowerCase();
if (code === "" || seen.has(code)) continue;
seen.add(code);
result.push(code);
}
return result;
}
function validPublicAPIBaseURL(value: string): boolean {
const trimmed = value.trim();
if (trimmed.length === 0) return true;
try {
const parsed = new URL(trimmed);
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") return false;
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}
export const settingsSchema = z.object({
server: z.object({
maxConcurrentRequests: positiveInteger.max(100_000),
}),
providerBuild: z.object({
baseURL: z.url(),
fallbackBaseURL: z.url().refine((value) => value.startsWith("https://")),
clientVersion: z.string().trim().min(1),
clientIdentifier: z.string().trim().min(1),
tokenAuth: z.string().trim().min(1),
tokenAuthConfigured: z.boolean(),
userAgent: z.string().trim().min(1),
responseHeaderTimeout: buildResponseHeaderDuration,
}),
providerWeb: z.object({
baseURL: z.url().refine((value) => value.startsWith("https://")),
statsigMode: z.enum(["manual", "url"]),
statsigManualValue: z.string().trim().max(4096),
statsigManualConfigured: z.boolean(),
statsigSignerURL: z.string().trim().max(2048),
clearanceMode: z.enum(["manual", "flaresolverr"]),
flareSolverrURL: z.string().trim().max(2048),
clearanceTimeout: durationSchema.refine((value) => durationSeconds(value) >= 10 && durationSeconds(value) <= 300),
clearanceRefresh: durationSchema.refine((value) => durationSeconds(value) >= 60 && durationSeconds(value) <= 86_400),
quotaTimeout: durationSchema, chatTimeout: durationSchema, imageTimeout: durationSchema, videoTimeout: durationSchema,
mediaConcurrency: positiveInteger.max(64), allowNSFW: z.boolean(),
recoveryBackoffBase: durationSchema, recoveryBackoffMax: durationSchema,
}).superRefine((value, context) => {
if (durationSeconds(value.recoveryBackoffMax) < durationSeconds(value.recoveryBackoffBase)) {
context.addIssue({ code: "custom", path: ["recoveryBackoffMax"], message: "invalid" });
}
if (value.statsigMode === "manual" && !value.statsigManualConfigured && value.statsigManualValue.length === 0) {
context.addIssue({ code: "custom", path: ["statsigManualValue"], message: "required" });
}
if (value.statsigManualValue.length > 0 && !validStatsigID(value.statsigManualValue)) {
context.addIssue({ code: "custom", path: ["statsigManualValue"], message: "invalid" });
}
if (value.statsigMode === "url") {
if (!validStatsigSignerURL(value.statsigSignerURL)) {
context.addIssue({ code: "custom", path: ["statsigSignerURL"], message: "invalid" });
}
}
if (value.clearanceMode === "flaresolverr" && !validHTTPURL(value.flareSolverrURL)) {
context.addIssue({ code: "custom", path: ["flareSolverrURL"], message: "invalid" });
}
}),
providerConsole: z.object({
baseURL: z.url().refine((value) => value.startsWith("https://")),
chatTimeout: consoleChatDuration,
}),
batch: z.object({
importConcurrency: positiveInteger.max(50),
conversionConcurrency: positiveInteger.max(50),
syncConcurrency: positiveInteger.max(50),
refreshConcurrency: positiveInteger.max(50),
randomDelay: z.number().int().min(0).max(5_000),
}),
media: z.object({
maxImageSize: byteSizeSchema.refine((value) => byteSizeBytes(value) >= 1 << 20 && byteSizeBytes(value) <= 32 << 20),
maxTotalSize: byteSizeSchema.refine((value) => byteSizeBytes(value) <= 2 ** 40),
cleanupThresholdPercent: z.number().int().min(50).max(95),
cleanupInterval: durationSchema.refine((value) => durationSeconds(value) >= 60 && durationSeconds(value) <= 86_400),
}).refine((value) => byteSizeBytes(value.maxTotalSize) >= byteSizeBytes(value.maxImageSize), { path: ["maxTotalSize"] }),
frontend: z.object({
publicApiBaseURL: z.string().trim().max(2048).refine((value) => validPublicAPIBaseURL(value), { message: "invalid" }),
}),
routing: z.object({
stickyTTL: routingTTLDuration,
cooldownBase: routingCooldownDuration,
cooldownMax: routingCooldownDuration,
capacityWait: routingCapacityWaitDuration,
maxAttempts: z.union([z.literal(UNLIMITED_ROUTING_ATTEMPTS), positiveInteger.max(65535)]),
preferFreeBuild: z.boolean(),
markBuildChatDeniedAsReauth: z.boolean(),
segmentedSelector: z.object({
enabled: z.boolean(),
minCandidates: z.number().int().min(100).max(1_000_000),
windowSize: z.number().int().min(8).max(256),
}),
}).refine((value) => durationSeconds(value.cooldownMax) >= durationSeconds(value.cooldownBase), { path: ["cooldownMax"] })
.refine((value) => value.segmentedSelector.windowSize <= value.segmentedSelector.minCandidates, { path: ["segmentedSelector", "windowSize"] }),
audit: z.object({ bufferSize: positiveInteger.max(262_144), batchSize: positiveInteger.max(4_096), flushInterval: auditFlushDuration, commitDelayMS: positiveInteger.max(50) })
.refine((value) => value.batchSize <= value.bufferSize, { path: ["batchSize"] }),
clientKeyDefaults: z.object({ rpmLimit: positiveInteger.max(100_000), maxConcurrent: positiveInteger.max(1_024) }),
accounts: z.object({
markBuildForbiddenReauth: z.boolean(),
buildForbiddenReauthCodes: z.string().superRefine((value, context) => {
const codes = parseForbiddenCodes(value);
if (codes.length === 0 || codes.length > 32 || codes.some((code) => !forbiddenCodePattern.test(code))) {
context.addIssue({ code: "custom", message: "invalid" });
}
}),
autoCleanReauthEnabled: z.boolean(),
autoCleanReauthInterval: durationSchema.refine((value) => {
const seconds = durationSeconds(value);
return seconds >= 60 && seconds <= 3_600;
}),
autoCleanReauthMinAge: durationSchema.refine((value) => {
const seconds = durationSeconds(value);
return seconds >= 60 && seconds <= 30 * 86_400;
}),
autoCleanIncludeDisabled: z.boolean(),
}),
});
export type SettingsForm = z.infer<typeof settingsSchema>;
export function toSettingsForm(config: SettingsConfigDTO): SettingsForm {
return {
server: config.server,
providerBuild: { ...config.providerBuild, responseHeaderTimeout: parseDuration(config.providerBuild.responseHeaderTimeout) },
providerWeb: {
...config.providerWeb,
statsigManualValue: "",
clearanceTimeout: parseDuration(config.providerWeb.clearanceTimeout), clearanceRefresh: parseDuration(config.providerWeb.clearanceRefresh),
quotaTimeout: parseDuration(config.providerWeb.quotaTimeout), chatTimeout: parseDuration(config.providerWeb.chatTimeout),
imageTimeout: parseDuration(config.providerWeb.imageTimeout), videoTimeout: parseDuration(config.providerWeb.videoTimeout),
recoveryBackoffBase: parseDuration(config.providerWeb.recoveryBackoffBase), recoveryBackoffMax: parseDuration(config.providerWeb.recoveryBackoffMax),
},
providerConsole: { ...config.providerConsole, chatTimeout: parseDuration(config.providerConsole.chatTimeout) },
batch: { ...config.batch, randomDelay: parseDurationMilliseconds(config.batch.randomDelay) },
media: {
maxImageSize: parseByteSize(config.media.maxImageBytes), maxTotalSize: parseByteSize(config.media.maxTotalBytes),
cleanupThresholdPercent: config.media.cleanupThresholdPercent,
cleanupInterval: parseDuration(config.media.cleanupInterval),
},
frontend: {
publicApiBaseURL: config.frontend.publicApiBaseURL,
},
routing: {
stickyTTL: parseDuration(config.routing.stickyTTL), cooldownBase: parseDuration(config.routing.cooldownBase),
cooldownMax: parseDuration(config.routing.cooldownMax), capacityWait: parseDuration(config.routing.capacityWait), maxAttempts: config.routing.maxAttempts,
preferFreeBuild: config.routing.preferFreeBuild,
markBuildChatDeniedAsReauth: config.routing.markBuildChatDeniedAsReauth,
segmentedSelector: config.routing.segmentedSelector,
},
audit: { bufferSize: config.audit.bufferSize, batchSize: config.audit.batchSize, flushInterval: parseDuration(config.audit.flushInterval), commitDelayMS: config.audit.commitDelayMS },
clientKeyDefaults: config.clientKeyDefaults,
accounts: {
markBuildForbiddenReauth: config.accounts.markBuildForbiddenReauth,
buildForbiddenReauthCodes: config.accounts.buildForbiddenReauthCodes.join("\n"),
autoCleanReauthEnabled: config.accounts.autoCleanReauthEnabled,
autoCleanReauthInterval: parseDuration(config.accounts.autoCleanReauthInterval),
autoCleanReauthMinAge: parseDuration(config.accounts.autoCleanReauthMinAge),
autoCleanIncludeDisabled: config.accounts.autoCleanIncludeDisabled,
},
};
}
export function toSettingsDTO(config: SettingsForm): SettingsConfigDTO {
return {
server: config.server,
providerBuild: { ...config.providerBuild, responseHeaderTimeout: formatDuration(config.providerBuild.responseHeaderTimeout) },
providerWeb: {
...config.providerWeb,
quotaTimeout: formatDuration(config.providerWeb.quotaTimeout), chatTimeout: formatDuration(config.providerWeb.chatTimeout),
imageTimeout: formatDuration(config.providerWeb.imageTimeout), videoTimeout: formatDuration(config.providerWeb.videoTimeout),
clearanceTimeout: formatDuration(config.providerWeb.clearanceTimeout), clearanceRefresh: formatDuration(config.providerWeb.clearanceRefresh),
recoveryBackoffBase: formatDuration(config.providerWeb.recoveryBackoffBase), recoveryBackoffMax: formatDuration(config.providerWeb.recoveryBackoffMax),
},
providerConsole: { ...config.providerConsole, chatTimeout: formatDuration(config.providerConsole.chatTimeout) },
batch: { ...config.batch, randomDelay: `${config.batch.randomDelay}ms` },
media: {
maxImageBytes: byteSizeBytes(config.media.maxImageSize), maxTotalBytes: byteSizeBytes(config.media.maxTotalSize),
cleanupThresholdPercent: config.media.cleanupThresholdPercent,
cleanupInterval: formatDuration(config.media.cleanupInterval),
},
frontend: {
publicApiBaseURL: config.frontend.publicApiBaseURL.trim(),
},
routing: {
stickyTTL: formatDuration(config.routing.stickyTTL), cooldownBase: formatDuration(config.routing.cooldownBase),
cooldownMax: formatDuration(config.routing.cooldownMax), capacityWait: formatDuration(config.routing.capacityWait), maxAttempts: config.routing.maxAttempts,
preferFreeBuild: config.routing.preferFreeBuild,
markBuildChatDeniedAsReauth: config.routing.markBuildChatDeniedAsReauth,
segmentedSelector: config.routing.segmentedSelector,
},
audit: { bufferSize: config.audit.bufferSize, batchSize: config.audit.batchSize, flushInterval: formatDuration(config.audit.flushInterval), commitDelayMS: config.audit.commitDelayMS },
clientKeyDefaults: config.clientKeyDefaults,
accounts: {
markBuildForbiddenReauth: config.accounts.markBuildForbiddenReauth,
buildForbiddenReauthCodes: parseForbiddenCodes(config.accounts.buildForbiddenReauthCodes),
autoCleanReauthEnabled: config.accounts.autoCleanReauthEnabled,
autoCleanReauthInterval: formatDuration(config.accounts.autoCleanReauthInterval),
autoCleanReauthMinAge: formatDuration(config.accounts.autoCleanReauthMinAge),
autoCleanIncludeDisabled: config.accounts.autoCleanIncludeDisabled,
},
};
}
export function isDurationUnit(value: string): value is DurationUnit {
return value === "s" || value === "m" || value === "h" || value === "d";
}
export function isByteSizeUnit(value: string): value is ByteSizeUnit {
return value === "MiB" || value === "GiB";
}
function byteSizeBytes(value: ByteSizeValue): number {
return Math.round(value.value * (value.unit === "GiB" ? 2 ** 30 : 2 ** 20));
}
function parseByteSize(bytes: number): ByteSizeValue {
if (bytes >= 2 ** 30 && bytes % 2 ** 30 === 0) return { value: bytes / 2 ** 30, unit: "GiB" };
return { value: bytes / 2 ** 20, unit: "MiB" };
}
function durationSeconds(value: DurationValue): number {
const factors: Record<DurationUnit, number> = { s: 1, m: 60, h: 3_600, d: 86_400 };
return value.value * factors[value.unit];
}
function formatDuration(value: DurationValue): string {
if (value.unit === "d") return `${value.value * 24}h`;
return `${value.value}${value.unit}`;
}
function parseDuration(value: string): DurationValue {
const simple = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)$/);
if (simple) {
const amount = Number(simple[1]);
if (simple[2] === "ms") return { value: amount / 1000, unit: "s" };
if (simple[2] === "h" && amount >= 24 && amount % 24 === 0) return { value: amount / 24, unit: "d" };
if (isDurationUnit(simple[2])) return { value: amount, unit: simple[2] };
}
const factors: Record<string, number> = { ns: 0.000001, us: 0.001, "µs": 0.001, ms: 1, s: 1000, m: 60_000, h: 3_600_000 };
const parts = [...value.matchAll(/(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/g)];
if (parts.map((part) => part[0]).join("") !== value || parts.length === 0) return { value: 1, unit: "s" };
const milliseconds = parts.reduce((total, part) => total + Number(part[1]) * factors[part[2]], 0);
const units: Array<[DurationUnit, number]> = [["d", 86_400_000], ["h", 3_600_000], ["m", 60_000], ["s", 1000]];
for (const [unit, factor] of units) {
const amount = milliseconds / factor;
if (amount >= 1 && Number.isInteger(amount)) return { value: amount, unit };
}
return { value: milliseconds / 1000, unit: "s" };
}
function parseDurationMilliseconds(value: string): number {
return Math.round(durationSeconds(parseDuration(value)) * 1000);
}
function validStatsigID(value: string): boolean {
try {
const normalized = value.trim().replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
return atob(padded).length === 70;
} catch {
return false;
}
}
function validStatsigSignerURL(value: string): boolean {
try {
const parsed = new URL(value);
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") return false;
const internal = internalSignerHostname(parsed.hostname);
if (internal) return parsed.protocol === "http:" || parsed.protocol === "https:";
return parsed.protocol === "https:" && (parsed.port === "" || parsed.port === "443");
} catch {
return false;
}
}
function validHTTPURL(value: string): boolean {
try {
const parsed = new URL(value);
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") return false;
const internal = internalSignerHostname(parsed.hostname);
if (internal) return parsed.protocol === "http:" || parsed.protocol === "https:";
return parsed.protocol === "https:" && (parsed.port === "" || parsed.port === "443");
} catch {
return false;
}
}
function internalSignerHostname(value: string): boolean {
const host = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
if (!host.includes(".")) {
if (host.includes(":")) return host === "::1" || /^(?:fc|fd|fe[89ab])/i.test(host);
return /^[a-z0-9](?:[a-z0-9_-]{0,61}[a-z0-9])?$/i.test(host);
}
const octets = host.split(".").map(Number);
if (octets.length !== 4 || octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
return octets[0] === 10 || octets[0] === 127 || octets[0] === 169 && octets[1] === 254 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168;
}