feat(video): add isolated drill-down cache

This commit is contained in:
Xiangzhe
2026-08-18 02:01:33 -03:00
parent edb3abf323
commit bb22eeba8d
6 changed files with 523 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
import { createErrorResponse } from "@/lib/api/errorResponse";
import {
VIDEO_BRIDGE_BROKER_PATH,
isVideoBridgeBrokerInternalRequest,
} from "@/lib/guardrails/videoBridgeBrokerAuth";
import {
VideoDrilldownCache,
type VideoDrilldownFrame,
} from "@/lib/guardrails/videoBridgeDrilldown";
import { resolveModelSyncInternalBaseUrl } from "@/shared/services/modelSyncScheduler";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export const VIDEO_BRIDGE_DRILLDOWN_PATH = "/api/modality-bridge/video/drilldown";
const MAX_BODY_BYTES = 34 * 1024 * 1024;
const drilldownCache = new VideoDrilldownCache({
maxEntries: 64,
ttlMs: 10 * 60 * 1000,
});
function expectedPath(): string {
const basePath = new URL(resolveModelSyncInternalBaseUrl()).pathname.replace(/\/$/, "");
return `${basePath}${VIDEO_BRIDGE_DRILLDOWN_PATH}`;
}
function invalid(message: string, status = 400): Response {
return createErrorResponse({ status, message, type: "invalid_request" });
}
function parseQuery(url: URL): {
endSeconds?: number;
frameCount?: number;
sessionId: string;
startSeconds?: number;
videoRef: string;
} | null {
const allowed = new Set(["end", "frames", "sessionId", "start", "videoRef"]);
if ([...url.searchParams.keys()].some((key) => !allowed.has(key))) return null;
const sessionId = url.searchParams.get("sessionId")?.trim() ?? "";
const videoRef = url.searchParams.get("videoRef")?.trim() ?? "";
if (!sessionId || !videoRef) return null;
const parseNumber = (name: string): number | undefined | null => {
const value = url.searchParams.get(name);
if (value === null) return undefined;
const parsed = Number(value);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
};
const startSeconds = parseNumber("start");
const endSeconds = parseNumber("end");
const rawFrameCount = url.searchParams.get("frames");
const frameCount =
rawFrameCount === null
? undefined
: /^\d{1,2}$/.test(rawFrameCount) && Number(rawFrameCount) >= 1 && Number(rawFrameCount) <= 16
? Number(rawFrameCount)
: null;
if (startSeconds === null || endSeconds === null || frameCount === null) return null;
return { endSeconds, frameCount, sessionId, startSeconds, videoRef };
}
interface VideoDrilldownRouteDependencies {
cache?: VideoDrilldownCache;
}
export async function handleVideoDrilldownRequest(
request: Request,
dependencies: VideoDrilldownRouteDependencies = {}
): Promise<Response> {
const url = new URL(request.url);
if (url.pathname !== expectedPath()) return invalid("Invalid Video Bridge drill-down path", 404);
if (!isVideoBridgeBrokerInternalRequest(request, VIDEO_BRIDGE_BROKER_PATH)) {
return invalid("This endpoint requires an authenticated internal loopback request", 403);
}
const cache = dependencies.cache ?? drilldownCache;
if (request.method === "GET") {
const query = parseQuery(url);
if (!query) return invalid("Invalid Video Bridge drill-down query");
const result = cache.get(query.sessionId, query.videoRef, query);
return result
? Response.json(result, { headers: { "Cache-Control": "no-store" } })
: invalid("Video Bridge drill-down result was not found", 404);
}
if (request.method === "DELETE") {
const sessionId = url.searchParams.get("sessionId")?.trim() ?? "";
if (!sessionId || [...url.searchParams.keys()].some((key) => key !== "sessionId")) {
return invalid("A sessionId is required");
}
return Response.json({ removed: cache.clearSession(sessionId) });
}
if (request.method !== "POST") return invalid("Invalid Video Bridge drill-down method", 405);
if (request.headers.get("content-type")?.toLowerCase() !== "application/json") {
return invalid("Video Bridge drill-down requires application/json");
}
const declaredLength = Number(request.headers.get("content-length"));
if (Number.isFinite(declaredLength) && declaredLength > MAX_BODY_BYTES) {
return invalid("Video Bridge drill-down payload is too large", 413);
}
let body: unknown;
try {
const bytes = await request.arrayBuffer();
if (bytes.byteLength > MAX_BODY_BYTES)
return invalid("Video Bridge drill-down payload is too large", 413);
body = JSON.parse(Buffer.from(bytes).toString("utf8"));
} catch {
return invalid("Video Bridge drill-down payload is invalid");
}
if (!body || typeof body !== "object")
return invalid("Video Bridge drill-down payload is invalid");
const record = body as Record<string, unknown>;
if (
typeof record.sessionId !== "string" ||
typeof record.videoRef !== "string" ||
typeof record.durationSeconds !== "number" ||
!Array.isArray(record.frames)
) {
return invalid("Video Bridge drill-down payload is invalid");
}
try {
cache.put(record.sessionId, record.videoRef, {
durationSeconds: record.durationSeconds,
frames: record.frames as VideoDrilldownFrame[],
});
} catch {
return invalid("Video Bridge drill-down payload is invalid");
}
return Response.json({ stored: true }, { status: 201, headers: { "Cache-Control": "no-store" } });
}
export async function POST(request: Request): Promise<Response> {
return handleVideoDrilldownRequest(request);
}
export async function GET(request: Request): Promise<Response> {
return handleVideoDrilldownRequest(request);
}
export async function DELETE(request: Request): Promise<Response> {
return handleVideoDrilldownRequest(request);
}

View File

@@ -0,0 +1,171 @@
import { createHash } from "node:crypto";
import { resolveVideoFocusWindow, type VideoFocusWindow } from "./videoBridgeRuntime";
export interface VideoDrilldownFrame {
dataUri: string;
timestampSeconds: number;
}
export interface VideoDrilldownPutValue {
durationSeconds: number;
frames: readonly VideoDrilldownFrame[];
}
export interface VideoDrilldownResult {
durationSeconds: number;
focusWindow?: VideoFocusWindow;
frames: VideoDrilldownFrame[];
}
export interface VideoDrilldownCacheOptions {
maxEntries: number;
now?: () => number;
ttlMs: number;
}
interface StoredDrilldown extends VideoDrilldownPutValue {
expiresAt: number;
sessionId: string;
}
const MAX_FRAME_BYTES = 4 * 1024 * 1024;
const MAX_TOTAL_BYTES = 32 * 1024 * 1024;
const MAX_DURATION_SECONDS = 600;
function cacheKey(sessionId: string, videoRef: string): string {
return createHash("sha256").update(`${sessionId}\0${videoRef}`).digest("hex");
}
function validateFrames(value: VideoDrilldownPutValue): VideoDrilldownFrame[] {
if (
!Number.isFinite(value.durationSeconds) ||
value.durationSeconds <= 0 ||
value.durationSeconds > MAX_DURATION_SECONDS ||
!Array.isArray(value.frames) ||
value.frames.length < 1 ||
value.frames.length > 16
) {
throw new Error("Invalid drill-down duration or frame count");
}
let totalBytes = 0;
const frames = value.frames.map((frame) => {
if (
!frame ||
!Number.isFinite(frame.timestampSeconds) ||
frame.timestampSeconds < 0 ||
frame.timestampSeconds > value.durationSeconds ||
!/^data:image\/jpeg;base64,[A-Za-z0-9+/=]+$/i.test(frame.dataUri)
) {
throw new Error("Invalid drill-down JPEG frame");
}
const encoded = frame.dataUri.slice(frame.dataUri.indexOf(",") + 1);
const bytes = Math.floor((encoded.length * 3) / 4);
if (bytes < 1 || bytes > MAX_FRAME_BYTES)
throw new Error("Drill-down frame byte limit exceeded");
totalBytes += bytes;
if (totalBytes > MAX_TOTAL_BYTES) throw new Error("Drill-down response byte limit exceeded");
return { dataUri: frame.dataUri, timestampSeconds: frame.timestampSeconds };
});
return frames.sort((left, right) => left.timestampSeconds - right.timestampSeconds);
}
export class VideoDrilldownCache {
private readonly entries = new Map<string, StoredDrilldown>();
private readonly now: () => number;
constructor(private readonly options: VideoDrilldownCacheOptions) {
if (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) {
throw new Error("Drill-down cache TTL must be positive");
}
if (!Number.isInteger(options.maxEntries) || options.maxEntries < 1) {
throw new Error("Drill-down cache entry limit is invalid");
}
this.now = options.now ?? Date.now;
}
put(sessionId: string, videoRef: string, value: VideoDrilldownPutValue): void {
if (!sessionId || sessionId.length > 128 || !videoRef || videoRef.length > 4096) {
throw new Error("Drill-down cache key is invalid");
}
const key = cacheKey(sessionId, videoRef);
this.entries.delete(key);
this.entries.set(key, {
durationSeconds: value.durationSeconds,
expiresAt: this.now() + this.options.ttlMs,
frames: validateFrames(value),
sessionId,
});
while (this.entries.size > this.options.maxEntries) {
const oldest = this.entries.keys().next().value;
if (oldest) this.entries.delete(oldest);
}
}
get(
sessionId: string,
videoRef: string,
options: { endSeconds?: number; frameCount?: number; startSeconds?: number } = {}
): VideoDrilldownResult | null {
const key = cacheKey(sessionId, videoRef);
const stored = this.entries.get(key);
if (!stored) return null;
if (stored.expiresAt <= this.now()) {
this.entries.delete(key);
return null;
}
this.entries.delete(key);
this.entries.set(key, stored);
const hasFocus = options.startSeconds !== undefined || options.endSeconds !== undefined;
let focusWindow: VideoFocusWindow | null = null;
try {
focusWindow = hasFocus
? resolveVideoFocusWindow(stored.durationSeconds, {
endSeconds: options.endSeconds,
startSeconds: options.startSeconds,
})
: null;
} catch {
return null;
}
const frameCount =
options.frameCount === undefined
? 16
: Number.isInteger(options.frameCount) &&
options.frameCount >= 1 &&
options.frameCount <= 16
? options.frameCount
: null;
if (frameCount === null) return null;
const frames = stored.frames
.filter(
(frame) =>
!focusWindow ||
(frame.timestampSeconds >= focusWindow.startSeconds &&
frame.timestampSeconds <= focusWindow.endSeconds)
)
.slice(0, frameCount)
.map((frame) => ({ ...frame }));
if (frames.length === 0) return null;
return {
durationSeconds: stored.durationSeconds,
...(focusWindow ? { focusWindow } : {}),
frames,
};
}
clearSession(sessionId: string): number {
let removed = 0;
for (const [key, entry] of this.entries.entries()) {
if (entry.sessionId === sessionId) {
this.entries.delete(key);
removed += 1;
}
}
return removed;
}
clearAll(): void {
this.entries.clear();
}
}