Ctrl K
ComunidadGuías…

src/dashboard/request-ring.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Bounded request ring for dashboard troubleshooting.
 *
 * One row per completed /v1/chat/completions call. The ring is deliberately
 * metadata-only: no headers, no prompt text, no upstream body. Callers are
 * identified by a short hash so an operator can correlate repeated requests
 * without the dashboard retaining an API key.
 *
 * The cursor is an opaque base64url of `${ts}:${id}`; rows are returned
 * newest-first. This is a process-local snapshot and resets on restart.
 */

import { createHash } from 'node:crypto';

const CAP = 2000;
const rows = [];
let next = 0;
let count = 0;
let seq = 0;

function callerRef(value) {
  if (!value) return null;
  return createHash('sha256').update(String(value)).digest('hex').slice(0, 8);
}

function encodeCursor(row) {
  return Buffer.from(`${row.ts}:${row.id}`, 'utf8').toString('base64url');
}

function decodeCursor(cursor) {
  if (!cursor) return null;
  try {
    const raw = Buffer.from(String(cursor), 'base64url').toString('utf8');
    const idx = raw.indexOf(':');
    if (idx <= 0) return null;
    const ts = Number(raw.slice(0, idx));
    const id = raw.slice(idx + 1);
    if (!Number.isFinite(ts) || !id) return null;
    return { ts, id };
  } catch {
    return null;
  }
}

/** Record one completed request. `row` is copied; unknown fields are dropped. */
export function recordRequest(row = {}) {
  const id = String(row.id || `ring_${++seq}`);
  const entry = {
    id,
    ts: Number.isFinite(row.ts) ? row.ts : Date.now(),
    model: typeof row.model === 'string' ? row.model.slice(0, 200) : null,
    selector: typeof row.selector === 'string' ? row.selector.slice(0, 200) : null,
    status: Number.isFinite(row.status) ? row.status : null,
    stream: !!row.stream,
    durationMs: Number.isFinite(row.durationMs) ? Math.max(0, Math.round(row.durationMs)) : null,
    class: typeof row.class === 'string' ? row.class.slice(0, 80) : null,
    protocol: typeof row.protocol === 'string' ? row.protocol.slice(0, 32) : null,
    caller: callerRef(row.caller),
  };
  rows[next] = entry;
  next = (next + 1) % CAP;
  count = Math.min(count + 1, CAP);
  return entry;
}

/**
 * Patch an existing row in place (used by streaming routes after the handler
 * finishes). Only the fields the ring owns are copied; unknown fields are
 * ignored. Returns the row or null when the id is no longer in the ring.
 */
export function updateRequest(id, patch = {}) {
  const key = String(id || '');
  if (!key) return null;
  const row = rows.find((r) => r && r.id === key);
  if (!row) return null;
  if (Number.isFinite(patch.durationMs)) row.durationMs = Math.max(0, Math.round(patch.durationMs));
  if (Number.isFinite(patch.status)) row.status = patch.status;
  if (typeof patch.class === 'string') row.class = patch.class.slice(0, 80);
  if (typeof patch.stream === 'boolean') row.stream = patch.stream;
  return row;
}

/** Newest-first page. `filter` supports model/status/class exact matches. */
export function listRequests({ cursor = null, limit = 50, filter = {} } = {}) {
  const n = Math.max(1, Math.min(200, Number(limit) || 50));
  const anchor = decodeCursor(cursor);
  const out = [];
  const start = count < CAP ? 0 : next;
  for (let i = 0; i < count; i++) {
    const idx = (start + count - 1 - i) % CAP;
    const row = rows[idx];
    if (!row) continue;
    if (anchor && !(row.ts < anchor.ts || (row.ts === anchor.ts && row.id < anchor.id))) continue;
    if (filter.model && row.model !== filter.model) continue;
    if (filter.status != null && row.status !== Number(filter.status)) continue;
    if (filter.class && row.class !== filter.class) continue;
    out.push(row);
    if (out.length >= n) break;
  }
  return {
    items: out,
    nextCursor: out.length === n ? encodeCursor(out[out.length - 1]) : null,
    total: count,
    capacity: CAP,
  };
}

export function requestRingStats() {
  return { total: count, capacity: CAP };
}

/** Test seam: empty the ring without touching production state. */
export function __resetRequestRingForTest() {
  rows.length = 0;
  next = 0;
  count = 0;
  seq = 0;
}