add stable request ids for replays

This commit is contained in:
2026-06-30 00:10:49 +08:00
parent 43097a22f1
commit ec1f8ceb16
4 changed files with 97 additions and 2 deletions
+22 -2
View File
@@ -2,6 +2,7 @@
import http from "node:http";
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
import fs from "node:fs";
import path from "node:path";
@@ -456,6 +457,7 @@ function openRequestsDatabase(dbPath) {
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS requests (
seq INTEGER PRIMARY KEY,
request_id TEXT,
started_at TEXT,
finished_at TEXT,
duration_ms INTEGER,
@@ -489,6 +491,12 @@ function openRequestsDatabase(dbPath) {
CREATE INDEX IF NOT EXISTS idx_requests_matched ON requests(matched);
CREATE INDEX IF NOT EXISTS idx_requests_response_stream ON requests(response_stream);
`);
const requestColumns = db.prepare("PRAGMA table_info(requests)").all();
const requestColumnNames = new Set(requestColumns.map((column) => column.name));
if (!requestColumnNames.has("request_id")) {
db.exec("ALTER TABLE requests ADD COLUMN request_id TEXT");
}
db.exec("CREATE INDEX IF NOT EXISTS idx_requests_request_id ON requests(request_id)");
return db;
}
@@ -507,10 +515,19 @@ function buildPersistedRequestPayload(entry) {
);
}
function computeRequestId(pathname, rawBody) {
const hash = createHash("sha256");
hash.update(normalizePath(pathname));
hash.update("\n");
hash.update(Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody || ""));
return `req_${hash.digest("hex").slice(0, 16)}`;
}
function requestRowFromEntry(entry) {
const payload = buildPersistedRequestPayload(entry);
return {
seq: entry.seq,
request_id: entry.request_id || null,
started_at: entry.started_at || null,
finished_at: entry.finished_at || null,
duration_ms: Number.isInteger(entry.duration_ms) ? entry.duration_ms : null,
@@ -543,13 +560,13 @@ function requestRowFromEntry(entry) {
function insertRequestRow(db, row) {
db.prepare(`
INSERT OR REPLACE INTO requests (
seq, started_at, finished_at, duration_ms, profile_name, method, path, model,
seq, request_id, started_at, finished_at, duration_ms, profile_name, method, path, model,
requested_model, forwarded_model, request_stream, response_stream, inspected, matched,
status_code, upstream_status_code, reasoning_tokens, input_tokens, output_tokens,
total_tokens, cached_tokens, error, upstream_origin, upstream_path,
upstream_auth_mode, upstream_auth_source, payload_json
) VALUES (
@seq, @started_at, @finished_at, @duration_ms, @profile_name, @method, @path, @model,
@seq, @request_id, @started_at, @finished_at, @duration_ms, @profile_name, @method, @path, @model,
@requested_model, @forwarded_model, @request_stream, @response_stream, @inspected, @matched,
@status_code, @upstream_status_code, @reasoning_tokens, @input_tokens, @output_tokens,
@total_tokens, @cached_tokens, @error, @upstream_origin, @upstream_path,
@@ -609,6 +626,7 @@ function buildRequestQueryFilters({ query, filter }) {
lower(coalesce(profile_name, '')) LIKE @query OR
lower(coalesce(method, '')) LIKE @query OR
lower(coalesce(path, '')) LIKE @query OR
lower(coalesce(request_id, '')) LIKE @query OR
lower(coalesce(model, '')) LIKE @query OR
lower(coalesce(requested_model, '')) LIKE @query OR
lower(coalesce(forwarded_model, '')) LIKE @query OR
@@ -1203,6 +1221,7 @@ async function buildPersistentRequestsSnapshot(runtime, { limit = 50, offset = 0
function buildRequestEntry({ seq, startedAt, startedMs, req, pathname, requestJson, profileName }) {
return {
seq,
request_id: null,
lifecycle_state: "sent",
started_at: startedAt.toISOString(),
first_response_at: null,
@@ -3060,6 +3079,7 @@ async function proxyRequest(runtime, req, res) {
const requestIsStream = Boolean(requestJson?.stream);
let totalUpstreamAttempts = 0;
requestEntry.request_body_bytes = rawRequestBody.length;
requestEntry.request_id = computeRequestId(pathname, rawRequestBody);
requestEntry.model = requestJson?.model || null;
requestEntry.requested_model = parsedRequestJson?.model || null;
requestEntry.forwarded_model = forwardedModel || parsedRequestJson?.model || null;